python310Packages.pydeconz: 104 -> 105
[NixPkgs.git] / nixos / tests / home-assistant.nix
blob0bbeffd18cf0d22db85c40e1813f455d237814d6
1 import ./make-test-python.nix ({ pkgs, lib, ... }:
3 let
4   configDir = "/var/lib/foobar";
5 in {
6   name = "home-assistant";
7   meta.maintainers = lib.teams.home-assistant.members;
9   nodes.hass = { pkgs, ... }: {
10     services.postgresql = {
11       enable = true;
12       ensureDatabases = [ "hass" ];
13       ensureUsers = [{
14         name = "hass";
15         ensurePermissions = {
16           "DATABASE hass" = "ALL PRIVILEGES";
17         };
18       }];
19     };
21     services.home-assistant = {
22       enable = true;
23       inherit configDir;
25       # tests loading components by overriding the package
26       package = (pkgs.home-assistant.override {
27         extraPackages = ps: with ps; [
28           colorama
29         ];
30         extraComponents = [ "zha" ];
31       }).overrideAttrs (oldAttrs: {
32         doInstallCheck = false;
33       });
35       # tests loading components from the module
36       extraComponents = [
37         "wake_on_lan"
38       ];
40       # test extra package passing from the module
41       extraPackages = python3Packages: with python3Packages; [
42         psycopg2
43       ];
45       config = {
46         homeassistant = {
47           name = "Home";
48           time_zone = "UTC";
49           latitude = "0.0";
50           longitude = "0.0";
51           elevation = 0;
52         };
54         # configure the recorder component to use the postgresql db
55         recorder.db_url = "postgresql://@/hass";
57         # we can't load default_config, because the updater requires
58         # network access and would cause an error, so load frontend
59         # here explicitly.
60         # https://www.home-assistant.io/integrations/frontend/
61         frontend = {};
63         # set up a wake-on-lan switch to test capset capability required
64         # for the ping suid wrapper
65         # https://www.home-assistant.io/integrations/wake_on_lan/
66         switch = [ {
67           platform = "wake_on_lan";
68           mac = "00:11:22:33:44:55";
69           host = "127.0.0.1";
70         } ];
72         # test component-based capability assignment (CAP_NET_BIND_SERVICE)
73         # https://www.home-assistant.io/integrations/emulated_hue/
74         emulated_hue = {
75           host_ip = "127.0.0.1";
76           listen_port = 80;
77         };
79         # https://www.home-assistant.io/integrations/logger/
80         logger = {
81           default = "info";
82         };
83       };
85       # configure the sample lovelace dashboard
86       lovelaceConfig = {
87         title = "My Awesome Home";
88         views = [{
89           title = "Example";
90           cards = [{
91             type = "markdown";
92             title = "Lovelace";
93             content = "Welcome to your **Lovelace UI**.";
94           }];
95         }];
96       };
97       lovelaceConfigWritable = true;
98     };
100     # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded.
101     specialisation.differentName = {
102       inheritParentConfig = true;
103       configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home";
104     };
106     # Cause a configuration change that requires a service restart as we added a new runtime dependency
107     specialisation.newFeature = {
108       inheritParentConfig = true;
109       configuration.services.home-assistant.config.esphome = {};
110     };
111   };
113   testScript = { nodes, ... }: let
114     system = nodes.hass.config.system.build.toplevel;
115   in
116   ''
117     import re
118     import json
120     start_all()
122     # Parse the package path out of the systemd unit, as we cannot
123     # access the final package, that is overriden inside the module,
124     # by any other means.
125     pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass")
126     response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1]
127     match = pattern.search(response)
128     assert match
129     package = match.group('path')
132     def get_journal_cursor(host) -> str:
133         exit, out = host.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
134         assert exit == 0
135         return json.loads(out)["__CURSOR"]
138     def wait_for_homeassistant(host, cursor):
139         host.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
142     hass.wait_for_unit("home-assistant.service")
143     cursor = get_journal_cursor(hass)
145     with subtest("Check that YAML configuration file is in place"):
146         hass.succeed("test -L ${configDir}/configuration.yaml")
148     with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
149         hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
151     with subtest("Check extraComponents and extraPackages are considered from the package"):
152         hass.succeed(f"grep -q 'colorama' {package}/extra_packages")
153         hass.succeed(f"grep -q 'zha' {package}/extra_components")
155     with subtest("Check extraComponents and extraPackages are considered from the module"):
156         hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages")
157         hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components")
159     with subtest("Check that Home Assistant's web interface and API can be reached"):
160         wait_for_homeassistant(hass, cursor)
161         hass.wait_for_open_port(8123)
162         hass.succeed("curl --fail http://localhost:8123/lovelace")
164     with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
165         hass.wait_for_open_port(80)
166         hass.succeed("curl --fail http://localhost:80/description.xml")
168     with subtest("Check extra components are considered in systemd unit hardening"):
169         hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
171     with subtest("Check service reloads when configuration changes"):
172       # store the old pid of the process
173       pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
174       cursor = get_journal_cursor(hass)
175       hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
176       new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
177       assert pid == new_pid, "The PID of the process should not change between process reloads"
178       wait_for_homeassistant(hass, cursor)
180     with subtest("check service restarts when package changes"):
181       pid = new_pid
182       cursor = get_journal_cursor(hass)
183       hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
184       new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
185       assert pid != new_pid, "The PID of the process shoudl change when the HA binary changes"
186       wait_for_homeassistant(hass, cursor)
188     with subtest("Check that no errors were logged"):
189         output_log = hass.succeed("cat ${configDir}/home-assistant.log")
190         assert "ERROR" not in output_log
192     with subtest("Check systemd unit hardening"):
193         hass.log(hass.succeed("systemctl cat home-assistant.service"))
194         hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
195   '';