Files
supervisor/tests/docker
dc6a77507b fix(docker): restore add-on device access after USB re-enumeration (#6877)
* fix(docker): register hw listener and match by-id paths for options-based devices

Two bugs caused a crash loop when a USB device re-enumerates to a different
minor number (e.g. ttyACM0→ttyACM1) after a HAOS reboot:

1. _hw_listener was only registered when addon.static_devices was non-empty.
   Addons that expose a device via the options schema (e.g. Z-Wave JS `device:`
   option) never had the listener registered, so add_devices_allowed was never
   called when the device reappeared at a new minor.

2. _hardware_events matched only device.path and device.sysfs against
   static_devices.  When static_devices (or the new options path) contains a
   by-id symlink, the match always failed because by-id paths live in
   device.links.

Fix: extend the listener registration condition to also cover addon.devices
(options-based), and expand the path-matching set to include device.links so
by-id paths resolve correctly.  For options-based devices, compare the incoming
Device against addon.devices (which re-evaluates options.json against the live
hardware list, picking up the new minor number automatically).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docker): use option_device_paths for cheap by-id hw event matching

Refactor _hardware_events to avoid per-event full options validation
(including pwnd hashing). Introduce AppOptions.extract_device_paths and
AppModel.option_device_paths to extract raw device paths from options
without resolving against live hardware. Use set-intersection against
{device.path, device.sysfs, *device.links} so by-id symlinks match
correctly after re-enumeration for both static and options-based devices.

Update test to use real schema/options setup and simulate a minor-number
change (ttyACM0→ttyACM1) with a stable by-id symlink.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: improve hw listener test coverage and add policy check

Address PR review feedback:
- Add hardware policy check in _hardware_events to prevent bypassing access
  restrictions on hotplug events (follows same pattern as startup cgroup setup)
- Fix test_app_options_device_hw_listener to properly simulate USB re-enumeration
  with different minor numbers (166:0 → 166:1)
- Add test_app_options_device_policy_check to verify policy enforcement for
  options-based devices
- Update TEST_HW_DEVICE with realistic major/minor attributes (166:0 for tty)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: mock HostFeature.OS_AGENT in hardware event tests

The _hardware_events method has a @Job decorator with conditions=[JobCondition.OS_AGENT],
which checks if HostFeature.OS_AGENT is in sys_host.features. Without mocking this,
the job conditions fail and the hardware event handler is never invoked, causing
add_devices_allowed to not be called and tests to fail.

Add patch.object(type(coresys.host), "features", ...) to all four hardware event tests
to ensure the OS_AGENT job condition is met.

Fixes test failures:
- test_app_new_device (all 6 parametrized cases)
- test_app_new_device_no_haos
- test_app_options_device_hw_listener
- test_app_options_device_policy_check

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: fix TEST_DEV_PATH to match TEST_HW_DEVICE.path

TEST_DEV_PATH was set to /dev/ttyAMA0 but TEST_HW_DEVICE.path is /dev/ttyACM0.
This mismatch would cause the dev_path=TEST_DEV_PATH parametrized test cases
to fail because the hardware event handler checks if the device path intersects
with the app's allowed devices, and "/dev/ttyAMA0" != "/dev/ttyACM0".

Update TEST_DEV_PATH from /dev/ttyAMA0 to /dev/ttyACM0 to match TEST_HW_DEVICE.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: mock option_device_paths in test_app_options_device_hw_listener

The test sets up schema and options but option_device_paths property may not
be working as expected in the test environment. Add an explicit mock for
option_device_paths to ensure it returns the by-id path, guaranteeing that:
1. The hardware listener is registered (checks option_device_paths at registration)
2. The device path matching works correctly in _hardware_events

This ensures the test properly validates that hardware events are processed
for options-based devices after re-enumeration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: make policy check test actually exercise the policy guard

test_app_options_device_policy_check set the device option via
persist["options"], but option_device_paths reads the merged options and
does not pick that override up during the test, so it returned an empty
set. The hardware event therefore failed the path-match guard and returned
before reaching the allowed_for_access check. The assert_not_called()
assertion then passed regardless of the policy outcome -- it would still
pass if the policy guard were removed entirely.

Mock option_device_paths to return the configured by-id path (mirroring
test_app_options_device_hw_listener) so the event device matches and
execution actually reaches the policy guard the test is meant to verify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add unit test for AppOptions.extract_device_paths

The integration tests exercise extract_device_paths only through a mocked
option_device_paths property, so the schema-walking logic introduced for
the hardware-event matching had no direct coverage.

Add a unit test that drives every schema shape the recursion handles --
flat, optional, filtered, list, nested dict and list of dicts -- and
asserts that non-device options, unset keys and empty values are skipped,
without requiring the devices to exist in hardware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-06-15 22:49:49 +02:00
..