feat: add dashboard API with quick links and widgets
Build and Push / build (release) Successful in 1m28s
Build and Push / build (release) Successful in 1m28s
- Dashboard domain with Quick Links CRUD + reorder endpoints - Dashboard widgets management endpoints - Database migrations for quick_links and dashboard_widgets tables - Static file controller for Organizr widgets - Default local user when OIDC is disabled - Domain-based architecture refactor (src/domains/, src/shared/) - Test suite updated for new structure (285 tests passing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
e85c9a123d
commit
381d43b60b
+37
-37
@@ -89,7 +89,7 @@ def sample_states():
|
||||
class TestHousekeepingHealth:
|
||||
"""Test /housekeeping/health endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_health_returns_200(self, mock_get_ha, client):
|
||||
"""Health endpoint should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -104,7 +104,7 @@ class TestHousekeepingHealth:
|
||||
response = client.get("/housekeeping/health")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_health_returns_connection_status(self, mock_get_ha, client):
|
||||
"""Health endpoint should return connection status."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -124,7 +124,7 @@ class TestHousekeepingHealth:
|
||||
assert "platform" in data
|
||||
assert data["platform"] == "home_assistant"
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_health_returns_unhealthy_when_disconnected(self, mock_get_ha, client):
|
||||
"""Health should report unhealthy when HA is disconnected."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -146,7 +146,7 @@ class TestHousekeepingHealth:
|
||||
class TestHousekeepingDevices:
|
||||
"""Test /housekeeping/devices endpoints."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_devices_returns_200(self, mock_get_ha, client, sample_states):
|
||||
"""List devices should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -156,7 +156,7 @@ class TestHousekeepingDevices:
|
||||
response = client.get("/housekeeping/devices")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_devices_returns_controllable_only(self, mock_get_ha, client, sample_states):
|
||||
"""List devices should filter out non-controllable entities."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -173,7 +173,7 @@ class TestHousekeepingDevices:
|
||||
assert "switch.garage" in entity_ids
|
||||
assert "sensor.temperature" not in entity_ids
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_devices_filter_by_domain(self, mock_get_ha, client, sample_states):
|
||||
"""List devices should filter by domain parameter."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -188,7 +188,7 @@ class TestHousekeepingDevices:
|
||||
for device in data["devices"]:
|
||||
assert device["domain"] == "light"
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_devices_includes_attributes(self, mock_get_ha, client, sample_states):
|
||||
"""List devices should include device attributes."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -204,7 +204,7 @@ class TestHousekeepingDevices:
|
||||
assert living_room["state"] == "on"
|
||||
assert "brightness" in living_room["attributes"]
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_get_device_returns_200(self, mock_get_ha, client):
|
||||
"""Get device should return 200 for existing device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -222,7 +222,7 @@ class TestHousekeepingDevices:
|
||||
response = client.get("/housekeeping/devices/light.living_room")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_get_device_returns_404_for_missing(self, mock_get_ha, client):
|
||||
"""Get device should return 404 for non-existent device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -239,7 +239,7 @@ class TestHousekeepingDevices:
|
||||
class TestHousekeepingAreas:
|
||||
"""Test /housekeeping/areas endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_areas_returns_200(self, mock_get_ha, client):
|
||||
"""List areas should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -252,7 +252,7 @@ class TestHousekeepingAreas:
|
||||
response = client.get("/housekeeping/areas")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_areas_returns_area_data(self, mock_get_ha, client):
|
||||
"""List areas should return area id and name."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -274,7 +274,7 @@ class TestHousekeepingAreas:
|
||||
class TestHousekeepingScenes:
|
||||
"""Test /housekeeping/scenes endpoints."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_scenes_returns_200(self, mock_get_ha, client, sample_states):
|
||||
"""List scenes should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -284,7 +284,7 @@ class TestHousekeepingScenes:
|
||||
response = client.get("/housekeeping/scenes")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_scenes_returns_only_scenes(self, mock_get_ha, client, sample_states):
|
||||
"""List scenes should only return scene entities."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -303,7 +303,7 @@ class TestHousekeepingScenes:
|
||||
class TestHousekeepingScripts:
|
||||
"""Test /housekeeping/scripts endpoints."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_scripts_returns_200(self, mock_get_ha, client, sample_states):
|
||||
"""List scripts should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -313,7 +313,7 @@ class TestHousekeepingScripts:
|
||||
response = client.get("/housekeeping/scripts")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_scripts_returns_only_scripts(self, mock_get_ha, client, sample_states):
|
||||
"""List scripts should only return script entities."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -331,7 +331,7 @@ class TestHousekeepingScripts:
|
||||
class TestHousekeepingAutomations:
|
||||
"""Test /housekeeping/automations endpoints."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_automations_returns_200(self, mock_get_ha, client, sample_states):
|
||||
"""List automations should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -341,7 +341,7 @@ class TestHousekeepingAutomations:
|
||||
response = client.get("/housekeeping/automations")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_list_automations_includes_enabled_status(self, mock_get_ha, client, sample_states):
|
||||
"""List automations should include enabled status."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -360,7 +360,7 @@ class TestHousekeepingAutomations:
|
||||
class TestHousekeepingHistory:
|
||||
"""Test /housekeeping/history endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_history_returns_200(self, mock_get_ha, client):
|
||||
"""History endpoint should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -373,7 +373,7 @@ class TestHousekeepingHistory:
|
||||
response = client.get("/housekeeping/history?entity_id=light.living_room")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_history_returns_entries(self, mock_get_ha, client):
|
||||
"""History endpoint should return history entries."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -391,13 +391,13 @@ class TestHousekeepingHistory:
|
||||
assert data["entity_id"] == "light.living_room"
|
||||
assert len(data["history"]) == 2
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_history_requires_entity_id(self, mock_get_ha, client):
|
||||
"""History endpoint should require entity_id parameter."""
|
||||
response = client.get("/housekeeping/history")
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_history_validates_hours_range(self, mock_get_ha, client):
|
||||
"""History endpoint should validate hours range (1-168)."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -415,7 +415,7 @@ class TestHousekeepingHistory:
|
||||
class TestHousekeepingDeviceControl:
|
||||
"""Test /housekeeping/devices/{entity_id}/control endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_turn_on(self, mock_get_ha, client):
|
||||
"""Control should turn on device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -433,7 +433,7 @@ class TestHousekeepingDeviceControl:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_turn_off(self, mock_get_ha, client):
|
||||
"""Control should turn off device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -451,7 +451,7 @@ class TestHousekeepingDeviceControl:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_toggle(self, mock_get_ha, client):
|
||||
"""Control should toggle device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -469,7 +469,7 @@ class TestHousekeepingDeviceControl:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_with_brightness(self, mock_get_ha, client):
|
||||
"""Control should set brightness."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -488,7 +488,7 @@ class TestHousekeepingDeviceControl:
|
||||
assert response.status_code == 200
|
||||
mock_client.turn_on.assert_called_once()
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_returns_404_for_missing(self, mock_get_ha, client):
|
||||
"""Control should return 404 for non-existent device."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -501,7 +501,7 @@ class TestHousekeepingDeviceControl:
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_control_device_returns_error_response(self, mock_get_ha, client):
|
||||
"""Control should return proper error response."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -523,7 +523,7 @@ class TestHousekeepingDeviceControl:
|
||||
class TestHousekeepingSceneActivation:
|
||||
"""Test /housekeeping/scenes/{scene_id}/activate endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_activate_scene_returns_200(self, mock_get_ha, client):
|
||||
"""Activate scene should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -533,7 +533,7 @@ class TestHousekeepingSceneActivation:
|
||||
response = client.post("/housekeeping/scenes/scene.movie_night/activate")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_activate_scene_returns_success_response(self, mock_get_ha, client):
|
||||
"""Activate scene should return success response."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -546,7 +546,7 @@ class TestHousekeepingSceneActivation:
|
||||
assert data["success"] is True
|
||||
assert data["scene_id"] == "scene.movie_night"
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_activate_scene_handles_error(self, mock_get_ha, client):
|
||||
"""Activate scene should handle errors."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -560,7 +560,7 @@ class TestHousekeepingSceneActivation:
|
||||
class TestHousekeepingScriptRun:
|
||||
"""Test /housekeeping/scripts/{script_id}/run endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_run_script_returns_200(self, mock_get_ha, client):
|
||||
"""Run script should return 200."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -570,7 +570,7 @@ class TestHousekeepingScriptRun:
|
||||
response = client.post("/housekeeping/scripts/script.bedtime/run")
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_run_script_returns_success_response(self, mock_get_ha, client):
|
||||
"""Run script should return success response."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -583,7 +583,7 @@ class TestHousekeepingScriptRun:
|
||||
assert data["success"] is True
|
||||
assert data["script_id"] == "script.bedtime"
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_run_script_handles_error(self, mock_get_ha, client):
|
||||
"""Run script should handle errors."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -597,7 +597,7 @@ class TestHousekeepingScriptRun:
|
||||
class TestHousekeepingAutomationToggle:
|
||||
"""Test /housekeeping/automations/{automation_id}/toggle endpoint."""
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_toggle_automation_enable(self, mock_get_ha, client):
|
||||
"""Toggle automation should enable when requested."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -610,7 +610,7 @@ class TestHousekeepingAutomationToggle:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_toggle_automation_disable(self, mock_get_ha, client):
|
||||
"""Toggle automation should disable when requested."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -624,7 +624,7 @@ class TestHousekeepingAutomationToggle:
|
||||
assert response.status_code == 200
|
||||
mock_client.disable_automation.assert_called_once()
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_toggle_automation_returns_new_state(self, mock_get_ha, client):
|
||||
"""Toggle automation should return new enabled state."""
|
||||
mock_client = AsyncMock()
|
||||
@@ -641,7 +641,7 @@ class TestHousekeepingAutomationToggle:
|
||||
assert data["automation_id"] == "automation.motion_lights"
|
||||
assert data["enabled"] is True
|
||||
|
||||
@patch("src.controllers.housekeeping_controller.get_homeassistant_client")
|
||||
@patch("src.domains.housekeeping.controller.get_homeassistant_client")
|
||||
def test_toggle_automation_handles_error(self, mock_get_ha, client):
|
||||
"""Toggle automation should handle errors."""
|
||||
mock_client = AsyncMock()
|
||||
|
||||
Reference in New Issue
Block a user