17 — Services — Subsystem Test Map¶
The services subsystem is the HA service-call layer: thin async handlers that
resolve call data, delegate to the manager, and wrap failures as
HomeAssistantError / ServiceValidationError (the HA Silver action-exception
contract). Covered by 189 tests across 14 files.
Source: custom_components/eufy_vacuum/services/
Architecture reference: docs/dev/02-ha-integration.md
Scope. This doc covers only the services/ package — the handlers wired by
async_register_services. The integration registers many more eufy_vacuum.*
services from sibling packages, and those are tested under their own subsystem
docs, not here:
- Map/segment/layout/live-pose services in
mapping/mapping_services.py(registered viaasync_register_mapping_services) — see 07-mapping.md. - Learning + external-run services in
learning/services.py— see 06-learning.md. - Theme services in
themes/services.py— see 14-themes.md.
Coverage map¶
| Source module | Stmts | Cov | Test file |
|---|---|---|---|
job_control.py |
127 | 95% | test_services_job_control_read.py, test_services_job_control_write.py |
run_profiles.py |
103 | 98% | test_services_run_profiles.py |
adapter_config.py |
96 | 94% | test_services_adapter_config.py |
setup.py |
128 | 91% | test_services_errors_setup.py |
dock.py |
80 | 100% | test_services_dock.py |
room_profiles.py |
80 | 100% | test_services_room_profiles.py |
rooms.py |
95 | 96% | test_services_rooms.py |
maintenance.py |
47 | 100% | test_services_maintenance_reset.py |
queue.py |
43 | 100% | test_services_queue.py |
snapshots.py |
43 | 100% | test_services_snapshots.py |
errors.py |
37 | 95% | test_services_errors_setup.py |
access_graph.py |
25 | 100% | test_services_access_graph.py |
_common.py |
35 | 100% | test_services_common.py, test_services_misc.py |
What's tested¶
- Read services — snapshots, job-control read, access-graph, saved profiles, dashboard snapshot: returned-shape assertions through the registry.
- Write services — job-control write, run-profile + room-profile + maintenance CRUD, queue build/clear, adapter-config set: side effects + persistence.
- Input validation — the
update_room_fieldscolor validator normalizes raw frontend input to canonical#rrggbb(#rgbexpansion, hash-prepend, lowercase, empty → clear) and rejects non-hex values at the service-schema boundary. - Error contract — a manager-layer failure surfaces as
HomeAssistantError(run-profile save/apply/rename/overwrite/delete, maintenance reset, set-interval save path), and not-found conditions raiseServiceValidationError. - Call-data resolution + job-finished payload (
_common) —resolved_call_datamap-id defaulting (incl. the no-active-map pass-through), andjob_finished_event_payloadbuilt from afinalize_result-wrapped result.
How it's tested¶
The manager_with_services fixture registers the full service set (same path as
async_setup_entry). Tests call hass.services.async_call(DOMAIN, name, data,
blocking=True, return_response=True). Error-contract tests monkeypatch a
manager method to raise and assert the wrapped exception type.
Known gaps¶
The remaining misses are almost all defensive, not untested behavior:
manager is Noneearly-returns (defensive) — the runtime-not-available guards at the top ofsetup.py'ssetup_get_map_rooms/setup_save_rooms/setup_reject_rooms/setup_force_remove_room/setup_set_panel_title/setup_set_map_camerahandlers, plusadapter_config.py's_handle_save_adapter_config/_handle_delete_adapter_config, return a{"status": "error"}stub or log-and-return. Unreachable in the fixture-registered service set, intentionally uncovered.- Parse / shape fallbacks (defensive) —
errors.py:84-85(limitint-parseexcept→ default 20). (_common.py's non-dictcompleted_jobguard and itsfinalize_result-shapedjob_pathextraction are now both covered.) - Registered-wrapper closures (trivial) —
rooms.py:234/246, thediscover_rooms/reconcile_roominner async wrappers; the_handle_*they delegate to are exercised directly. (The two remainingrooms.pybranch edges are thevol.Requiredvacuum_entity_idfalse-arms the registered service can't reach.)
Note adapter_config.py:68-78 (missing adapter_id / missing dispatch.template
guards) are # pragma: no cover by design and so are excluded from the miss list.
Module coverage: setup.py 90%, adapter_config.py 94%, errors.py 95%,
rooms.py 96%; the remaining nine modules (including run_profiles.py and
_common.py, now at 100%) are at 100%. The handler success + error contracts
are covered.