about summary refs log tree commit diff
path: root/pkgs/servers/home-assistant
diff options
context:
space:
mode:
authorRobert Schütz <nix@dotlambda.de>2022-01-15 00:20:08 +0000
committerRobert Schütz <nix@dotlambda.de>2022-01-16 00:51:45 +0000
commit61265ec0b4176678aa776d901ace3d5191728b65 (patch)
tree2efb538761d0f5d7e65d8843721f11788d322726 /pkgs/servers/home-assistant
parent6054fb82d5e5351d6432f57bd66de64723fe53ba (diff)
home-assistant: outsource component tests
A component's tests can now be run by building
    home-assistant.tests.components.${component}

Co-authored-by: Martin Weinelt <hexa@darmstadt.ccc.de>
Diffstat (limited to 'pkgs/servers/home-assistant')
-rw-r--r--pkgs/servers/home-assistant/component-packages.nix567
-rw-r--r--pkgs/servers/home-assistant/default.nix646
-rwxr-xr-xpkgs/servers/home-assistant/parse-requirements.py19
-rw-r--r--pkgs/servers/home-assistant/patches/tests-mock-source-ip.patch4
-rw-r--r--pkgs/servers/home-assistant/tests.nix69
5 files changed, 678 insertions, 627 deletions
diff --git a/pkgs/servers/home-assistant/component-packages.nix b/pkgs/servers/home-assistant/component-packages.nix
index 1a24fc7504ebe..aab639140e5ff 100644
--- a/pkgs/servers/home-assistant/component-packages.nix
+++ b/pkgs/servers/home-assistant/component-packages.nix
@@ -725,7 +725,7 @@
     "rpi_gpio" = ps: with ps; [ ]; # missing inputs: RPi.GPIO
     "rpi_gpio_pwm" = ps: with ps; [ ]; # missing inputs: pwmled
     "rpi_pfio" = ps: with ps; [ ]; # missing inputs: pifacecommon pifacedigitalio
-    "rpi_power" = ps: with ps; [ ]; # missing inputs: rpi-bad-power
+    "rpi_power" = ps: with ps; [ rpi-bad-power ];
     "rpi_rf" = ps: with ps; [ ]; # missing inputs: RPi.GPIO rpi-rf
     "rss_feed_template" = ps: with ps; [ aiohttp-cors ];
     "rtorrent" = ps: with ps; [ ];
@@ -1018,4 +1018,569 @@
     "zwave" = ps: with ps; [ homeassistant-pyozw pydispatcher ];
     "zwave_js" = ps: with ps; [ aiohttp-cors pyserial pyudev zwave-js-server-python ];
   };
+  # components listed in tests/components for which all dependencies are packaged
+  supportedComponentsWithTests = [
+    "abode"
+    "accuweather"
+    "acmeda"
+    "adax"
+    "adguard"
+    "advantage_air"
+    "aemet"
+    "agent_dvr"
+    "air_quality"
+    "airly"
+    "airnow"
+    "airthings"
+    "airtouch4"
+    "airvisual"
+    "alarm_control_panel"
+    "alarmdecoder"
+    "alert"
+    "alexa"
+    "almond"
+    "ambee"
+    "amberelectric"
+    "ambiclimate"
+    "ambient_station"
+    "analytics"
+    "androidtv"
+    "apache_kafka"
+    "api"
+    "apple_tv"
+    "apprise"
+    "aprs"
+    "arcam_fmj"
+    "arlo"
+    "asuswrt"
+    "atag"
+    "august"
+    "aurora"
+    "auth"
+    "automation"
+    "awair"
+    "aws"
+    "axis"
+    "azure_devops"
+    "azure_event_hub"
+    "balboa"
+    "bayesian"
+    "binary_sensor"
+    "blackbird"
+    "blebox"
+    "blink"
+    "blueprint"
+    "bluetooth_le_tracker"
+    "bmw_connected_drive"
+    "bond"
+    "bosch_shc"
+    "braviatv"
+    "broadlink"
+    "brother"
+    "bsblan"
+    "buienradar"
+    "button"
+    "caldav"
+    "calendar"
+    "camera"
+    "canary"
+    "cast"
+    "cert_expiry"
+    "climacell"
+    "climate"
+    "cloud"
+    "cloudflare"
+    "color_extractor"
+    "comfoconnect"
+    "command_line"
+    "compensation"
+    "config"
+    "configurator"
+    "control4"
+    "conversation"
+    "coolmaster"
+    "coronavirus"
+    "counter"
+    "cover"
+    "crownstone"
+    "daikin"
+    "darksky"
+    "datadog"
+    "debugpy"
+    "deconz"
+    "default_config"
+    "demo"
+    "denonavr"
+    "derivative"
+    "device_automation"
+    "device_sun_light_trigger"
+    "device_tracker"
+    "devolo_home_control"
+    "devolo_home_network"
+    "dexcom"
+    "dhcp"
+    "dialogflow"
+    "directv"
+    "discovery"
+    "dlna_dmr"
+    "doorbird"
+    "dsmr"
+    "dte_energy_bridge"
+    "duckdns"
+    "dunehd"
+    "eafm"
+    "ecobee"
+    "econet"
+    "efergy"
+    "elgato"
+    "elkm1"
+    "emonitor"
+    "emulated_hue"
+    "emulated_kasa"
+    "emulated_roku"
+    "energy"
+    "enocean"
+    "enphase_envoy"
+    "environment_canada"
+    "epson"
+    "esphome"
+    "everlights"
+    "evil_genius_labs"
+    "ezviz"
+    "faa_delays"
+    "facebook"
+    "facebox"
+    "fail2ban"
+    "fan"
+    "feedreader"
+    "ffmpeg"
+    "fido"
+    "file"
+    "filesize"
+    "filter"
+    "fireservicerota"
+    "firmata"
+    "fjaraskupan"
+    "flick_electric"
+    "flipr"
+    "flo"
+    "flume"
+    "flunearyou"
+    "flux"
+    "flux_led"
+    "folder"
+    "folder_watcher"
+    "foobot"
+    "forecast_solar"
+    "foscam"
+    "freebox"
+    "freedns"
+    "freedompro"
+    "fritz"
+    "fritzbox"
+    "fritzbox_callmonitor"
+    "fronius"
+    "frontend"
+    "garages_amsterdam"
+    "gdacs"
+    "generic"
+    "generic_hygrostat"
+    "generic_thermostat"
+    "geo_json_events"
+    "geo_location"
+    "geo_rss_events"
+    "geofency"
+    "geonetnz_quakes"
+    "geonetnz_volcano"
+    "gios"
+    "glances"
+    "goalzero"
+    "gogogate2"
+    "google"
+    "google_assistant"
+    "google_domains"
+    "google_pubsub"
+    "google_translate"
+    "google_travel_time"
+    "google_wifi"
+    "gpslogger"
+    "graphite"
+    "gree"
+    "group"
+    "growatt_server"
+    "guardian"
+    "habitica"
+    "hangouts"
+    "harmony"
+    "hassio"
+    "hddtemp"
+    "heos"
+    "here_travel_time"
+    "hisense_aehw4a1"
+    "history"
+    "history_stats"
+    "hive"
+    "hlk_sw16"
+    "home_connect"
+    "home_plus_control"
+    "homeassistant"
+    "homekit"
+    "homekit_controller"
+    "homematic"
+    "homematicip_cloud"
+    "honeywell"
+    "html5"
+    "http"
+    "huawei_lte"
+    "hue"
+    "huisbaasje"
+    "humidifier"
+    "hunterdouglas_powerview"
+    "hvv_departures"
+    "hyperion"
+    "ialarm"
+    "iaqualink"
+    "icloud"
+    "ifttt"
+    "ign_sismologia"
+    "image"
+    "image_processing"
+    "imap_email_content"
+    "influxdb"
+    "input_boolean"
+    "input_datetime"
+    "input_number"
+    "input_select"
+    "input_text"
+    "insteon"
+    "integration"
+    "intent"
+    "intent_script"
+    "ios"
+    "iotawatt"
+    "ipma"
+    "ipp"
+    "iqvia"
+    "islamic_prayer_times"
+    "isy994"
+    "izone"
+    "jellyfin"
+    "jewish_calendar"
+    "juicenet"
+    "keenetic_ndms2"
+    "kira"
+    "kmtronic"
+    "knx"
+    "kodi"
+    "konnected"
+    "kraken"
+    "kulersky"
+    "lastfm"
+    "lcn"
+    "light"
+    "litterrobot"
+    "local_file"
+    "local_ip"
+    "locative"
+    "lock"
+    "logbook"
+    "logentries"
+    "logger"
+    "london_air"
+    "lookin"
+    "lovelace"
+    "luftdaten"
+    "lutron_caseta"
+    "lyric"
+    "mailbox"
+    "manual"
+    "manual_mqtt"
+    "maxcube"
+    "mazda"
+    "media_player"
+    "media_source"
+    "melcloud"
+    "meraki"
+    "met"
+    "met_eireann"
+    "meteoclimatic"
+    "mhz19"
+    "microsoft_face"
+    "microsoft_face_detect"
+    "microsoft_face_identify"
+    "mikrotik"
+    "mill"
+    "min_max"
+    "minecraft_server"
+    "minio"
+    "mobile_app"
+    "modbus"
+    "modem_callerid"
+    "modern_forms"
+    "mold_indicator"
+    "moon"
+    "motion_blinds"
+    "motioneye"
+    "mqtt"
+    "mqtt_eventstream"
+    "mqtt_json"
+    "mqtt_room"
+    "mqtt_statestream"
+    "mullvad"
+    "mutesync"
+    "my"
+    "myq"
+    "mysensors"
+    "mythicbeastsdns"
+    "nam"
+    "namecheapdns"
+    "nanoleaf"
+    "neato"
+    "ness_alarm"
+    "nest"
+    "netatmo"
+    "network"
+    "nexia"
+    "nightscout"
+    "no_ip"
+    "notify"
+    "notion"
+    "nsw_rural_fire_service_feed"
+    "nuki"
+    "number"
+    "nws"
+    "nx584"
+    "octoprint"
+    "omnilogic"
+    "onboarding"
+    "ondilo_ico"
+    "openalpr_cloud"
+    "openalpr_local"
+    "openerz"
+    "opengarage"
+    "openhardwaremonitor"
+    "opentherm_gw"
+    "openuv"
+    "openweathermap"
+    "opnsense"
+    "ovo_energy"
+    "owntracks"
+    "ozw"
+    "p1_monitor"
+    "panel_custom"
+    "panel_iframe"
+    "persistent_notification"
+    "person"
+    "philips_js"
+    "pi_hole"
+    "picnic"
+    "ping"
+    "plaato"
+    "plant"
+    "plex"
+    "plugwise"
+    "point"
+    "poolsense"
+    "profiler"
+    "prometheus"
+    "prosegur"
+    "proximity"
+    "push"
+    "pushbullet"
+    "pvpc_hourly_pricing"
+    "python_script"
+    "qld_bushfire"
+    "rachio"
+    "radarr"
+    "rainforest_eagle"
+    "rainmachine"
+    "random"
+    "rdw"
+    "recollect_waste"
+    "recorder"
+    "reddit"
+    "remote"
+    "renault"
+    "rest"
+    "rest_command"
+    "rflink"
+    "rfxtrx"
+    "ridwell"
+    "ring"
+    "risco"
+    "rituals_perfume_genie"
+    "rmvtransport"
+    "roku"
+    "roomba"
+    "roon"
+    "rpi_power"
+    "rss_feed_template"
+    "ruckus_unleashed"
+    "safe_mode"
+    "samsungtv"
+    "scene"
+    "screenlogic"
+    "script"
+    "search"
+    "season"
+    "select"
+    "sense"
+    "sensor"
+    "sentry"
+    "seventeentrack"
+    "sharkiq"
+    "shell_command"
+    "shelly"
+    "shopping_list"
+    "sia"
+    "sigfox"
+    "sighthound"
+    "simplisafe"
+    "simulated"
+    "siren"
+    "slack"
+    "sleepiq"
+    "sma"
+    "smappee"
+    "smart_meter_texas"
+    "smarthab"
+    "smartthings"
+    "smarttub"
+    "smhi"
+    "smtp"
+    "snips"
+    "solaredge"
+    "solarlog"
+    "soma"
+    "somfy"
+    "somfy_mylink"
+    "sonarr"
+    "songpal"
+    "sonos"
+    "soundtouch"
+    "spaceapi"
+    "spc"
+    "speedtestdotnet"
+    "spider"
+    "spotify"
+    "sql"
+    "squeezebox"
+    "srp_energy"
+    "ssdp"
+    "starline"
+    "startca"
+    "statistics"
+    "statsd"
+    "stream"
+    "stt"
+    "subaru"
+    "sun"
+    "surepetcare"
+    "switch"
+    "switchbot"
+    "switcher_kis"
+    "syncthing"
+    "syncthru"
+    "synology_dsm"
+    "system_bridge"
+    "system_health"
+    "system_log"
+    "tado"
+    "tag"
+    "tailscale"
+    "tasmota"
+    "tcp"
+    "telegram"
+    "tellduslive"
+    "template"
+    "tesla_wall_connector"
+    "threshold"
+    "tibber"
+    "tile"
+    "time_date"
+    "timer"
+    "tod"
+    "tolo"
+    "tomato"
+    "toon"
+    "totalconnect"
+    "tplink"
+    "traccar"
+    "trace"
+    "tractive"
+    "tradfri"
+    "trafikverket_weatherstation"
+    "transmission"
+    "transport_nsw"
+    "trend"
+    "tts"
+    "tuya"
+    "twentemilieu"
+    "twilio"
+    "twinkly"
+    "twitch"
+    "uk_transport"
+    "unifi"
+    "unifi_direct"
+    "universal"
+    "upb"
+    "upcloud"
+    "updater"
+    "upnp"
+    "uptime"
+    "uptimerobot"
+    "usb"
+    "usgs_earthquakes_feed"
+    "utility_meter"
+    "uvc"
+    "vacuum"
+    "velbus"
+    "venstar"
+    "vera"
+    "verisure"
+    "version"
+    "vesync"
+    "vicare"
+    "vilfo"
+    "vizio"
+    "vlc_telnet"
+    "voicerss"
+    "volumio"
+    "vultr"
+    "wake_on_lan"
+    "wallbox"
+    "water_heater"
+    "watttime"
+    "waze_travel_time"
+    "weather"
+    "webhook"
+    "webostv"
+    "websocket_api"
+    "wemo"
+    "whirlpool"
+    "wiffi"
+    "wilight"
+    "wled"
+    "workday"
+    "worldclock"
+    "wsdot"
+    "xbox"
+    "xiaomi"
+    "xiaomi_aqara"
+    "xiaomi_miio"
+    "yale_smart_alarm"
+    "yamaha"
+    "yamaha_musiccast"
+    "yandex_transport"
+    "yandextts"
+    "yeelight"
+    "youless"
+    "zeroconf"
+    "zerproc"
+    "zha"
+    "zodiac"
+    "zone"
+    "zwave"
+    "zwave_js"
+  ];
 }
diff --git a/pkgs/servers/home-assistant/default.nix b/pkgs/servers/home-assistant/default.nix
index 1a67003bdce48..1cf23984adb5e 100644
--- a/pkgs/servers/home-assistant/default.nix
+++ b/pkgs/servers/home-assistant/default.nix
@@ -1,5 +1,6 @@
 { stdenv
 , lib
+, callPackage
 , fetchFromGitHub
 , fetchpatch
 , python3
@@ -157,7 +158,7 @@ let
       });
     };
 
-  py = python3.override {
+  python = python3.override {
     # Put packageOverrides at the start so they are applied after defaultOverrides
     packageOverrides = lib.foldr lib.composeExtensions (self: super: { }) ([ packageOverrides ] ++ defaultOverrides);
   };
@@ -166,22 +167,24 @@ let
 
   availableComponents = builtins.attrNames componentPackages.components;
 
-  getPackages = component: builtins.getAttr component componentPackages.components;
+  inherit (componentPackages) supportedComponentsWithTests;
 
-  componentBuildInputs = lib.concatMap (component: getPackages component py.pkgs) extraComponents;
+  getPackages = component: componentPackages.components.${component};
+
+  componentBuildInputs = lib.concatMap (component: getPackages component python.pkgs) extraComponents;
 
   # Ensure that we are using a consistent package set
-  extraBuildInputs = extraPackages py.pkgs;
+  extraBuildInputs = extraPackages python.pkgs;
 
   # Don't forget to run parse-requirements.py after updating
   hassVersion = "2021.12.9";
 
-in with py.pkgs; buildPythonApplication rec {
+in python.pkgs.buildPythonApplication rec {
   pname = "homeassistant";
   version = assert (componentPackages.version == hassVersion); hassVersion;
 
   # check REQUIRED_PYTHON_VER in homeassistant/const.py
-  disabled = pythonOlder "3.8";
+  disabled = python.pythonOlder "3.8";
 
   # don't try and fail to strip 6600+ python files, it takes minutes!
   dontStrip = true;
@@ -217,7 +220,7 @@ in with py.pkgs; buildPythonApplication rec {
     substituteInPlace tests/test_config.py --replace '"/usr"' '"/build/media"'
   '';
 
-  propagatedBuildInputs = [
+  propagatedBuildInputs = with python.pkgs; [
     # Only packages required in setup.py
     aiohttp
     astral
@@ -253,9 +256,10 @@ in with py.pkgs; buildPythonApplication rec {
   # upstream only tests on Linux, so do we.
   doCheck = stdenv.isLinux;
 
-  checkInputs = [
+  checkInputs = with python.pkgs; [
     # test infrastructure (selectively from requirement_test.txt)
     freezegun
+    jsonpickle
     pytest-aiohttp
     pytest-freezegun
     pytest-mock
@@ -264,541 +268,15 @@ in with py.pkgs; buildPythonApplication rec {
     pytest-xdist
     pytestCheckHook
     requests-mock
-    stdlib-list
-    jsonpickle
     respx
+    stdlib-list
+    tqdm
     # required by tests/auth/mfa_modules
     pyotp
-  ] ++ lib.concatMap (component: getPackages component py.pkgs) componentTests;
-
-  # We can reasonably test components that don't communicate with any network
-  # services. Before adding new components to this list make sure we have all
-  # its dependencies packaged and listed in ./component-packages.nix.
-  componentTests = [
-    "abode"
-    "accuweather"
-    "acmeda"
-    "adguard"
-    "advantage_air"
-    "aemet"
-    "agent_dvr"
-    "air_quality"
-    "airly"
-    "airnow"
-    "airthings"
-    "airvisual"
-    "alarm_control_panel"
-    "alarmdecoder"
-    "alert"
-    "alexa"
-    "almond"
-    "ambiclimate"
-    "ambient_station"
-    "analytics"
-    "androidtv"
-    "apache_kafka"
-    "api"
-    "apple_tv"
-    "apprise"
-    "aprs"
-    "arcam_fmj"
-    "arlo"
-    "asuswrt"
-    "atag"
-    "august"
-    "aurora"
-    "auth"
-    "automation"
-    "awair"
-    "aws"
-    "axis"
-    "azure_devops"
-    "azure_event_hub"
-    "bayesian"
-    "binary_sensor"
-    "blackbird"
-    "blebox"
-    "blink"
-    "blueprint"
-    "bluetooth_le_tracker"
-    "bmw_connected_drive"
-    "bond"
-    "bosch_shc"
-    "braviatv"
-    "broadlink"
-    "brother"
-    "bsblan"
-    "buienradar"
-    "caldav"
-    "calendar"
-    "camera"
-    "canary"
-    "cast"
-    "cert_expiry"
-    "climacell"
-    "climate"
-    "cloud"
-    "cloudflare"
-    "color_extractor"
-    "comfoconnect"
-    "command_line"
-    "compensation"
-    "config"
-    "configurator"
-    "control4"
-    "conversation"
-    "coolmaster"
-    "coronavirus"
-    "counter"
-    "cover"
-    "daikin"
-    "darksky"
-    "datadog"
-    "deconz"
+  ] ++ lib.concatMap (component: getPackages component python.pkgs) [
+    # some components are needed even if tests in tests/components are disabled
     "default_config"
-    "demo"
-    "denonavr"
-    "derivative"
-    "device_automation"
-    "device_sun_light_trigger"
-    "device_tracker"
-    "devolo_home_control"
-    "dexcom"
-    "dhcp"
-    "dialogflow"
-    "directv"
-    "discovery"
-    "doorbird"
-    "dsmr"
-    "dte_energy_bridge"
-    "duckdns"
-    "dunehd"
-    "eafm"
-    "ecobee"
-    "econet"
-    "efergy"
-    "elgato"
-    "elkm1"
-    "emonitor"
-    "emulated_hue"
-    "emulated_kasa"
-    "emulated_roku"
-    "enocean"
-    "enphase_envoy"
-    "epson"
-    "esphome"
-    "everlights"
-    "ezviz"
-    "faa_delays"
-    "facebook"
-    "facebox"
-    "fail2ban"
-    "fan"
-    "feedreader"
-    "ffmpeg"
-    "fido"
-    "file"
-    "filesize"
-    "filter"
-    "fireservicerota"
-    "firmata"
-    "fjaraskupan"
-    "flick_electric"
-    "flipr"
-    "flo"
-    "flume"
-    "flunearyou"
-    "flux"
-    "folder"
-    "folder_watcher"
-    "foobot"
-    "foscam"
-    "freebox"
-    "freedns"
-    "fritz"
-    "fritzbox"
-    "fritzbox_callmonitor"
-    "frontend"
-    "garages_amsterdam"
-    "gdacs"
-    "generic"
-    "generic_thermostat"
-    "geo_json_events"
-    "geo_location"
-    "geo_rss_events"
-    "geofency"
-    "geonetnz_quakes"
-    "geonetnz_volcano"
-    "gios"
-    # updated to incompatible version and overriding is annoying because of async_timeout<4 pin
-    # "glances"
-    "goalzero"
-    "gogogate2"
-    "google"
-    "google_assistant"
-    "google_domains"
-    "google_pubsub"
-    "google_translate"
-    "google_travel_time"
-    "google_wifi"
-    "gpslogger"
-    "graphite"
-    "gree"
-    "group"
-    "growatt_server"
-    "guardian"
-    "habitica"
-    "hangouts"
-    "harmony"
-    "hassio"
-    "hddtemp"
-    "heos"
-    "here_travel_time"
-    "hisense_aehw4a1"
-    "history"
-    "history_stats"
-    "hive"
-    "hlk_sw16"
-    "home_connect"
-    "home_plus_control"
-    "homeassistant"
-    # disable homekit tests because they fail in the network component
-    #"homekit"
-    "homekit_controller"
-    "homematic"
-    "homematicip_cloud"
-    "honeywell"
-    "html5"
-    "http"
-    "huawei_lte"
     "hue"
-    "huisbaasje"
-    "humidifier"
-    "hunterdouglas_powerview"
-    "hvv_departures"
-    "hyperion"
-    "ialarm"
-    "iaqualink"
-    "icloud"
-    "ifttt"
-    "ign_sismologia"
-    "image"
-    "image_processing"
-    "imap_email_content"
-    "influxdb"
-    "input_boolean"
-    "input_datetime"
-    "input_number"
-    "input_select"
-    "input_text"
-    "insteon"
-    "integration"
-    "intent"
-    "intent_script"
-    "ios"
-    "ipma"
-    "ipp"
-    "iqvia"
-    "islamic_prayer_times"
-    "isy994"
-    "izone"
-    "jewish_calendar"
-    "juicenet"
-    "keenetic_ndms2"
-    "kira"
-    "kmtronic"
-    "knx"
-    "kodi"
-    "konnected"
-    "kraken"
-    "kulersky"
-    "lastfm"
-    "lcn"
-    "light"
-    "litterrobot"
-    "local_file"
-    "local_ip"
-    "locative"
-    "lock"
-    "logbook"
-    "logentries"
-    "logger"
-    "london_air"
-    "lovelace"
-    "luftdaten"
-    "lutron_caseta"
-    "lyric"
-    "mailbox"
-    "manual"
-    "manual_mqtt"
-    "maxcube"
-    "mazda"
-    "media_player"
-    "media_source"
-    "melcloud"
-    "meraki"
-    "met"
-    "met_eireann"
-    "meteoclimatic"
-    "mhz19"
-    "microsoft_face"
-    "microsoft_face_detect"
-    "microsoft_face_identify"
-    "mikrotik"
-    "mill"
-    "min_max"
-    "minecraft_server"
-    "minio"
-    "mobile_app"
-    "modbus"
-    "mold_indicator"
-    "moon"
-    "motion_blinds"
-    "motioneye"
-    "mqtt"
-    "mqtt_eventstream"
-    "mqtt_json"
-    "mqtt_room"
-    "mqtt_statestream"
-    "mullvad"
-    "mutesync"
-    "my"
-    "myq"
-    "mysensors"
-    "mythicbeastsdns"
-    "nam"
-    "namecheapdns"
-    "neato"
-    "ness_alarm"
-    # python-nest has an unfree license, this prevents builds through ofborg
-    # "nest"
-    "netatmo"
-    "nexia"
-    "nightscout"
-    "no_ip"
-    "notify"
-    "notion"
-    "nsw_rural_fire_service_feed"
-    "nuki"
-    "number"
-    "nws"
-    "nx584"
-    "octoprint"
-    "omnilogic"
-    "onboarding"
-    "ondilo_ico"
-    "openalpr_cloud"
-    "openalpr_local"
-    "openerz"
-    "openhardwaremonitor"
-    "opentherm_gw"
-    "openuv"
-    "openweathermap"
-    "opnsense"
-    "ovo_energy"
-    "owntracks"
-    "ozw"
-    "p1_monitor"
-    "panel_custom"
-    "panel_iframe"
-    "persistent_notification"
-    "person"
-    "philips_js"
-    "pi_hole"
-    "picnic"
-    "ping"
-    "plaato"
-    "plant"
-    "plex"
-    "plugwise"
-    "point"
-    "poolsense"
-    "profiler"
-    "prometheus"
-    "proximity"
-    "push"
-    "pushbullet"
-    "pvpc_hourly_pricing"
-    "python_script"
-    "qld_bushfire"
-    "rachio"
-    "radarr"
-    "rainmachine"
-    "random"
-    "recollect_waste"
-    "recorder"
-    "reddit"
-    "remote"
-    "renault"
-    "rest"
-    "rest_command"
-    "rflink"
-    "rfxtrx"
-    "ring"
-    "risco"
-    "rituals_perfume_genie"
-    "rmvtransport"
-    "roku"
-    "roomba"
-    "roon"
-    "rss_feed_template"
-    "ruckus_unleashed"
-    "safe_mode"
-    "samsungtv"
-    "scene"
-    "screenlogic"
-    "script"
-    "search"
-    "season"
-    "sense"
-    "sensor"
-    "sentry"
-    "sharkiq"
-    "shell_command"
-    "shelly"
-    "shopping_list"
-    "sia"
-    "sigfox"
-    "sighthound"
-    "simplisafe"
-    "simulated"
-    "slack"
-    "sleepiq"
-    "sma"
-    "smappee"
-    "smart_meter_texas"
-    "smarthab"
-    "smartthings"
-    "smarttub"
-    "smhi"
-    "smtp"
-    "snips"
-    "solaredge"
-    "soma"
-    "somfy"
-    "somfy_mylink"
-    "sonarr"
-    "songpal"
-    # disable sonos components test because they rely on ssdp, which doesn't work in our sandbox
-    # "sonos"
-    "soundtouch"
-    "spaceapi"
-    "spc"
-    "speedtestdotnet"
-    "spider"
-    "spotify"
-    "sql"
-    "squeezebox"
-    "srp_energy"
-    "ssdp"
-    "starline"
-    "startca"
-    "statistics"
-    "statsd"
-    "stream"
-    "stt"
-    "subaru"
-    "sun"
-    "surepetcare"
-    "switch"
-    "switcher_kis"
-    "syncthing"
-    "syncthru"
-    "synology_dsm"
-    "system_health"
-    "system_log"
-    "tado"
-    "tag"
-    "tasmota"
-    "tcp"
-    "telegram"
-    "tellduslive"
-    "template"
-    "threshold"
-    "tibber"
-    "tile"
-    "time_date"
-    "timer"
-    "tod"
-    "tomato"
-    "toon"
-    "totalconnect"
-    "tplink"
-    "traccar"
-    "trace"
-    "tradfri"
-    "transmission"
-    "transport_nsw"
-    "trend"
-    "tts"
-    "tuya"
-    "twentemilieu"
-    "twilio"
-    "twinkly"
-    "twitch"
-    "uk_transport"
-    "unifi"
-    "unifi_direct"
-    "universal"
-    "upb"
-    "upcloud"
-    "updater"
-    # disabled, because it tries to join a multicast group and fails to find a usable network interface
-    # "upnp"
-    "uptime"
-    "uptimerobot"
-    "usgs_earthquakes_feed"
-    "utility_meter"
-    "uvc"
-    "vacuum"
-    "velbus"
-    # disabled, because it includes onewire component tests, for which we lack p1wire dependency
-    # "venstar"
-    "vera"
-    "verisure"
-    "version"
-    "vesync"
-    "vilfo"
-    "vizio"
-    "vlc_telnet"
-    "voicerss"
-    "volumio"
-    "vultr"
-    "wake_on_lan"
-    "wallbox"
-    "water_heater"
-    "waze_travel_time"
-    "weather"
-    "webhook"
-    "webostv"
-    "websocket_api"
-    "wemo"
-    "wiffi"
-    "wilight"
-    "wled"
-    "workday"
-    "worldclock"
-    "wsdot"
-    "xbox"
-    "xiaomi"
-    "xiaomi_aqara"
-    # disabled, because we require cryptography>=35.0 for the miio package
-    # "xiaomi_miio"
-    "yamaha"
-    "yandex_transport"
-    "yandextts"
-    "yeelight"
-    "youless"
-    # disabled, because it tries to join a multicast group and fails to find a usable network interface
-    # "zeroconf"
-    "zerproc"
-    "zha"
-    "zodiac"
-    "zone"
-    "zwave"
-    "zwave_js"
-  ] ++ lib.optionals (builtins.any (s: s == stdenv.hostPlatform.system) debugpy.meta.platforms) [
-    "debugpy"
   ];
 
   pytestFlagsArray = [
@@ -811,120 +289,46 @@ in with py.pkgs; buildPythonApplication rec {
     "--only-rerun RuntimeError"
     # enable full variable printing on error
     "--showlocals"
-    # here_travel_time/test_sensor.py: Tries to access HERE API: herepy.error.HEREError: Error occured on __get
-    "--deselect tests/components/here_travel_time/test_sensor.py::test_invalid_credentials"
-    # screenlogic/test_config_flow.py: Tries to send out UDP broadcasts
-    "--deselect tests/components/screenlogic/test_config_flow.py::test_form_cannot_connect"
-    # abode/test_camera.py: Race condition in pickle file creationg
-    "--deselect tests/components/abode/test_camera.py::test_camera_off"
-    # asuswrt/test_config_flow.py: Sandbox network limitations, fails with unexpected error
-    "--deselect tests/components/asuswrt/test_config_flow.py::test_on_connect_failed"
-    # shelly/test_config_flow.py: Tries to join multicast group
-    "--deselect tests/components/shelly/test_config_flow.py::test_form"
-    "--deselect tests/components/shelly/test_config_flow.py::test_title_without_name"
-    "--deselect tests/components/shelly/test_config_flow.py::test_form_auth"
-    "--deselect tests/components/shelly/test_config_flow.py::test_form_errors_test_connection"
-    "--deselect tests/components/shelly/test_config_flow.py::test_user_setup_ignored_device"
-    "--deselect tests/components/shelly/test_config_flow.py::test_form_auth_errors_test_connection"
-    "--deselect tests/components/shelly/test_config_flow.py::test_form_auth_errors_test_connection"
-    "--deselect tests/components/shelly/test_config_flow.py::test_form_auth_errors_test_connection"
-    "--deselect tests/components/shelly/test_config_flow.py::test_zeroconf"
-    "--deselect tests/components/shelly/test_config_flow.py::test_zeroconf_sleeping_device"
-    "--deselect tests/components/shelly/test_config_flow.py::test_zeroconf_sleeping_device_error"
-    "--deselect tests/components/shelly/test_config_flow.py::test_zeroconf_sleeping_device_error"
-    "--deselect tests/components/shelly/test_config_flow.py::test_zeroconf_require_auth"
-    # prometheus/test_init.py: Spurious AssertionError regarding humidifier_target_humidity_percent metric
-    "--deselect tests/components/prometheus/test_init.py::test_view"
-    # smhi/test_init.py: Tries to fetch data from the network: socket.gaierror: [Errno -2] Name or service not known
-    "--deselect tests/components/smhi/test_init.py::test_remove_entry"
-    # wallbox/test_config_flow.py: Tries to connect to api.wall-box.cim: Failed to establish a new connection: [Errno -2] Name or service not known
-    "--deselect tests/components/wallbox/test_config_flow.py::test_form_invalid_auth"
-    "--deselect tests/components/wallbox/test_config_flow.py::test_form_cannot_connect"
-    # default_config/test_init.py: Tries to check for updates and fails ungracefully without network access
-    "--deselect tests/components/default_config/test_init.py::test_setup"
-    # local_ip/test_{init,config_flow}.py: tries to lookup a route towards a multicast address and fails
-    "--deselect tests/components/local_ip/test_init.py::test_basic_setup"
-    "--deselect tests/components/local_ip/test_config_flow.py::test_config_flow"
-    # netatmo/test_select.py: NoneType object has no attribute state
-    "--deselect tests/components/netatmo/test_select.py::test_select_schedule_thermostats"
-    # wemo/test_sensor.py: KeyError for various power attributes
-    "--deselect tests/components/wemo/test_sensor.py::TestInsightTodayEnergy::test_state_unavailable"
-    "--deselect tests/components/wemo/test_sensor.py::TestInsightCurrentPower::test_state_unavailable"
     # helpers/test_system_info.py: AssertionError: assert 'Unknown' == 'Home Assistant Container'
     "--deselect tests/helpers/test_system_info.py::test_container_installationtype"
     # tests are located in tests/
     "tests"
-    # dynamically add packages required for component tests
-  ] ++ map (component: "tests/components/" + component) componentTests;
+  ];
 
   disabledTestPaths = [
     # don't bulk test all components
     "tests/components"
     # pyotp since v2.4.0 complains about the short mock keys, hass pins v2.3.0
     "tests/auth/mfa_modules/test_notify.py"
-    # emulated_hue/test_upnp.py: Tries to establish the public ipv4 address
-    "tests/components/emulated_hue/test_upnp.py"
-    # tado/test_{climate,water_heater}.py: Tries to connect to my.tado.com
-    "tests/components/tado/test_climate.py"
-    "tests/components/tado/test_water_heater.py"
   ];
 
   disabledTests = [
     # AssertionError: assert 1 == 0
-    "test_error_posted_as_event"
     "test_merge"
-    # ModuleNotFoundError: No module named 'pyqwikswitch'
-    "test_merge_id_schema"
-    # keyring.errors.NoKeyringError: No recommended backend was available.
-    "test_secrets_from_unrelated_fails"
-    "test_secrets_credstash"
-    # generic/test_camera.py: AssertionError: 500 == 200
-    "test_fetching_without_verify_ssl"
-    "test_fetching_url_with_verify_ssl"
-    # util/test_package.py: AssertionError on package.is_installed('homeassistant>=999.999.999')
-    "test_check_package_version_does_not_match"
-    # homeassistant/util/thread.py:51: SystemError
-    "test_executor_shutdown_can_interrupt_threads"
-    # {'theme_color': '#03A9F4'} != {'theme_color': 'blue'}
-    "test_webhook_handle_get_config"
-    # onboarding tests rpi_power component, for which we are lacking rpi_bad_power library
-    "test_onboarding_core_sets_up_rpi_power"
-    "test_onboarding_core_no_rpi_power"
-    # hue/test_sensor_base.py: Race condition when counting events
-    "test_hue_events"
-    # august/test_lock.py: AssertionError: assert 'unlocked' == 'locked' / assert 'off' == 'on'
-    "test_lock_update_via_pubnub"
-    "test_door_sense_update_via_pubnub"
     # Tests are flaky
     "test_config_platform_valid"
-    "test_hls_stream"
   ];
 
   preCheck = ''
     export HOME="$TEMPDIR"
 
-    patch -p1 < ${./patches/tests-mock-source-ip.patch}
-
     # the tests require the existance of a media dir
     mkdir /build/media
 
     # put ping binary into PATH, e.g. for wake_on_lan tests
     export PATH=${inetutils}/bin:$PATH
-
-    # error out when component test directory is missing, otherwise hidden by xdist execution :(
-    for component in ${lib.concatStringsSep " " (map lib.escapeShellArg componentTests)}; do
-      test -d "tests/components/$component" || {
-        >2& echo "ERROR: Tests for component '$component' were enabled, but they do not exist!"
-        exit 1
-      }
-    done
   '';
 
   passthru = {
-    inherit availableComponents extraComponents;
-    python = py;
+    inherit
+      availableComponents
+      extraComponents
+      getPackages
+      python
+      supportedComponentsWithTests;
     tests = {
-      inherit (nixosTests) home-assistant;
+      nixos = nixosTests.home-assistant;
+      components = callPackage ./tests.nix { };
     };
   };
 
diff --git a/pkgs/servers/home-assistant/parse-requirements.py b/pkgs/servers/home-assistant/parse-requirements.py
index 4a2c42ff370c6..fbe46b2377876 100755
--- a/pkgs/servers/home-assistant/parse-requirements.py
+++ b/pkgs/servers/home-assistant/parse-requirements.py
@@ -62,6 +62,7 @@ def get_version():
 
 def parse_components(version: str = "master"):
     components = {}
+    components_with_tests = []
     with tempfile.TemporaryDirectory() as tmp:
         with urlopen(
             f"https://github.com/home-assistant/home-assistant/archive/{version}.tar.gz"
@@ -69,9 +70,13 @@ def parse_components(version: str = "master"):
             tarfile.open(fileobj=BytesIO(response.read())).extractall(tmp)
         # Use part of a script from the Home Assistant codebase
         core_path = os.path.join(tmp, f"core-{version}")
+
+        for entry in os.scandir(os.path.join(core_path, "tests/components")):
+            if entry.is_dir():
+                components_with_tests.append(entry.name)
+
         sys.path.append(core_path)
         from script.hassfest.model import Integration
-
         integrations = Integration.load_dir(
             pathlib.Path(
                 os.path.join(core_path, "homeassistant/components")
@@ -81,7 +86,8 @@ def parse_components(version: str = "master"):
             integration = integrations[domain]
             if not integration.disabled:
                 components[domain] = integration.manifest
-    return components
+
+    return components, components_with_tests
 
 
 # Recursively get the requirements of a component and its dependencies
@@ -162,7 +168,7 @@ def main() -> None:
     packages = dump_packages()
     version = get_version()
     print("Generating component-packages.nix for version {}".format(version))
-    components = parse_components(version=version)
+    components, components_with_tests = parse_components(version=version)
     build_inputs = {}
     outdated = {}
     for component in sorted(components.keys()):
@@ -205,6 +211,13 @@ def main() -> None:
                 f.write(f" # missing inputs: {' '.join(missing)}")
             f.write("\n")
         f.write("  };\n")
+        f.write("  # components listed in tests/components for which all dependencies are packaged\n")
+        f.write("  supportedComponentsWithTests = [\n")
+        for component, deps in build_inputs.items():
+            available, missing = deps
+            if len(missing) == 0 and component in components_with_tests:
+                f.write(f'    "{component}"' + "\n")
+        f.write("  ];\n")
         f.write("}\n")
 
     supported_components = reduce(lambda n, c: n + (build_inputs[c][1] == []),
diff --git a/pkgs/servers/home-assistant/patches/tests-mock-source-ip.patch b/pkgs/servers/home-assistant/patches/tests-mock-source-ip.patch
index 4094d08ee7ff8..6812ee1915bb0 100644
--- a/pkgs/servers/home-assistant/patches/tests-mock-source-ip.patch
+++ b/pkgs/servers/home-assistant/patches/tests-mock-source-ip.patch
@@ -1,8 +1,8 @@
 diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py
-index 7cc864727d..69333a5454 100644
+index b3ef88e7ab..b7a8471e1a 100644
 --- a/homeassistant/components/network/__init__.py
 +++ b/homeassistant/components/network/__init__.py
-@@ -26,7 +26,7 @@ async def async_get_source_ip(
+@@ -30,7 +30,7 @@ async def async_get_source_ip(
  ) -> str:
      """Get the source ip for a target ip."""
      adapters = await async_get_adapters(hass)
diff --git a/pkgs/servers/home-assistant/tests.nix b/pkgs/servers/home-assistant/tests.nix
new file mode 100644
index 0000000000000..8e552ed15e0b3
--- /dev/null
+++ b/pkgs/servers/home-assistant/tests.nix
@@ -0,0 +1,69 @@
+{ lib
+, home-assistant
+}:
+
+let
+  # some components' tests have additional dependencies
+  extraCheckInputs = with home-assistant.python.pkgs; {
+    alexa = [ ha-av ];
+    camera = [ ha-av ];
+    cloud = [ mutagen ];
+    config = [ pydispatcher ];
+    generic = [ ha-av ];
+    google_translate = [ mutagen ];
+    nest = [ ha-av ];
+    onboarding = [ pymetno rpi-bad-power ];
+    voicerss = [ mutagen ];
+    yandextts = [ mutagen ];
+    zha = [ pydeconz ];
+    zwave_js = [ homeassistant-pyozw ];
+  };
+
+  extraDisabledTestPaths = {
+    tado = [
+      # tado/test_{climate,water_heater}.py: Tries to connect to my.tado.com
+      "tests/components/tado/test_climate.py"
+      "tests/components/tado/test_water_heater.py"
+    ];
+  };
+
+  extraPytestFlagsArray = {
+    asuswrt = [
+      # asuswrt/test_config_flow.py: Sandbox network limitations, fails with unexpected error
+      "--deselect tests/components/asuswrt/test_config_flow.py::test_on_connect_failed"
+    ];
+  };
+in lib.listToAttrs (map (component: lib.nameValuePair component (
+  home-assistant.overridePythonAttrs (old: {
+    pname = "homeassistant-test-${component}";
+
+    dontBuild = true;
+    dontInstall = true;
+
+    checkInputs = old.checkInputs
+      ++ home-assistant.getPackages component home-assistant.python.pkgs
+      ++ extraCheckInputs.${component} or [ ];
+
+    disabledTestPaths = old.disabledTestPaths ++ extraDisabledTestPaths.${component} or [ ];
+
+    pytestFlagsArray = lib.remove "tests" old.pytestFlagsArray
+      ++ extraPytestFlagsArray.${component} or [ ]
+      ++ [ "tests/components/${component}" ];
+
+    preCheck = old.preCheck + lib.optionalString (component != "network") ''
+      patch -p1 < ${./patches/tests-mock-source-ip.patch}
+    '';
+
+    meta = old.meta // {
+      broken = lib.elem component [
+        "airtouch4"
+        "glances"
+        "ridwell"
+        "venstar"
+        "yamaha_musiccast"
+      ];
+      # upstream only tests on Linux, so do we.
+      platforms = lib.platforms.linux;
+    };
+  })
+)) home-assistant.supportedComponentsWithTests)