Add "Mein Zimmer" room-control page (Home Assistant, proxied through the backend)
Implements the room-control page from the design mockup: a scenes row above cards for shutters, color lamps, and brightness-only lamps, all driven by a new `general.ha` config section (server URL, token, ordered device/scene lists). The backend proxies every Home Assistant call server-side (GET/POST /api/ha/...) rather than the browser calling Home Assistant directly, so the long-lived token never leaves the LAN device and Home Assistant's own CORS settings don't need to know about musicmouse at all. Card kind (shutter/color/brightness-only) is inferred at runtime from what Home Assistant reports about each entity, not configured explicitly. Also stops tracking python-backend/config.yml, which had drifted into the repo despite its own header saying it shouldn't be - it now carries real credentials locally and needs to stay untracked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,75 @@ def test_web_section_is_optional(config_dir: Path) -> None:
|
||||
assert config.general.web.static_dir == (config_dir / "dist").resolve()
|
||||
|
||||
|
||||
def test_ha_section_is_optional(config_dir: Path) -> None:
|
||||
assert load_config(write_config(config_dir, VALID_CONFIG)).general.ha is None
|
||||
|
||||
|
||||
def test_ha_device_and_scene_name_is_optional(config_dir: Path) -> None:
|
||||
data = _config(
|
||||
ha={
|
||||
"url": "http://homeassistant.local:8123",
|
||||
"token": "abc123",
|
||||
"devices": [{"entity_id": "light.a"}],
|
||||
"scenes": [{"entity_id": "scene.a"}],
|
||||
}
|
||||
)
|
||||
config = load_config(write_config(config_dir, data))
|
||||
assert config.general.ha is not None
|
||||
assert config.general.ha.devices[0].name is None
|
||||
assert config.general.ha.scenes[0].name is None
|
||||
|
||||
|
||||
def test_ha_devices_and_scenes_preserve_config_order(config_dir: Path) -> None:
|
||||
data = _config(
|
||||
ha={
|
||||
"url": "http://homeassistant.local:8123",
|
||||
"token": "abc123",
|
||||
"devices": [
|
||||
{"entity_id": "cover.rollo"},
|
||||
{"entity_id": "light.deckenlampe"},
|
||||
{"entity_id": "light.beyond_links"},
|
||||
],
|
||||
"scenes": [
|
||||
{"entity_id": "scene.aufwachen"},
|
||||
{"entity_id": "scene.lesen"},
|
||||
],
|
||||
}
|
||||
)
|
||||
config = load_config(write_config(config_dir, data))
|
||||
ha = config.general.ha
|
||||
assert ha is not None
|
||||
assert [d.entity_id for d in ha.devices] == [
|
||||
"cover.rollo",
|
||||
"light.deckenlampe",
|
||||
"light.beyond_links",
|
||||
]
|
||||
assert [s.entity_id for s in ha.scenes] == ["scene.aufwachen", "scene.lesen"]
|
||||
|
||||
|
||||
def test_ha_requires_devices_or_scenes(config_dir: Path) -> None:
|
||||
message = _error(
|
||||
config_dir,
|
||||
_config(ha={"url": "http://ha", "token": "abc", "devices": [], "scenes": []}),
|
||||
)
|
||||
assert "configure at least one device or scene" in message
|
||||
|
||||
|
||||
def test_ha_rejects_unknown_key_on_a_device(config_dir: Path) -> None:
|
||||
message = _error(
|
||||
config_dir,
|
||||
_config(
|
||||
ha={
|
||||
"url": "http://ha",
|
||||
"token": "abc",
|
||||
"devices": [{"entity_id": "light.a", "kind": "beyond"}],
|
||||
"scenes": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
assert "unknown option" in message
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- error paths
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user