Files
bambuddy/backend/tests/unit/test_homeassistant_settings.py
bambuman eda1f5a9e7 Home Assistant: add environment variable configuration support
- Add HA_URL and HA_TOKEN environment variables for automatic HA
  integration configuration in HA add-on deployments
- Environment variables always override database settings with
  non-negotiable precedence; database values preserved for fallback
- Auto-enable integration when both env vars are set; partial config
  (one env var) uses database enable state without auto-enabling
- Add centralized get_homeassistant_settings() function following
  Spoolman pattern; replace direct database queries across codebase
- Add ha_url_from_env, ha_token_from_env, ha_env_managed fields to
  AppSettings schema to inform frontend about configuration source
- UI shows read-only fields with lock icons and "(Environment Managed)"
  labels when env-controlled; toggle shows auto-enable badge
- Add comprehensive test coverage: 9 integration + 8 unit tests

Closes #283
2026-02-07 19:01:04 +02:00

230 lines
8.8 KiB
Python

"""Unit tests for Home Assistant settings with environment variable support.
Tests the get_homeassistant_settings() function in isolation.
"""
import os
from unittest.mock import AsyncMock, patch
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_no_env_vars():
"""Test get_homeassistant_settings with no environment variables."""
from backend.app.api.routes.settings import get_homeassistant_settings
# Mock database session
db = AsyncMock(spec=AsyncSession)
# Mock get_setting to return database values
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "true",
}.get(key, "")
# Ensure no env vars
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("HA_URL", None)
os.environ.pop("HA_TOKEN", None)
result = await get_homeassistant_settings(db)
# Should use database values
assert result["ha_url"] == "http://db-url:8123"
assert result["ha_token"] == "db-token"
assert result["ha_enabled"] is True
assert result["ha_url_from_env"] is False
assert result["ha_token_from_env"] is False
assert result["ha_env_managed"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_with_env_vars():
"""Test get_homeassistant_settings with environment variables set."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "false",
}.get(key, "")
# Set environment variables
with patch.dict(os.environ, {"HA_URL": "http://supervisor/core", "HA_TOKEN": "env-token"}, clear=False):
result = await get_homeassistant_settings(db)
# Should use environment values
assert result["ha_url"] == "http://supervisor/core"
assert result["ha_token"] == "env-token"
assert result["ha_enabled"] is True # Auto-enabled
assert result["ha_url_from_env"] is True
assert result["ha_token_from_env"] is True
assert result["ha_env_managed"] is True
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_partial_env_url_only():
"""Test get_homeassistant_settings with only HA_URL set."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "false",
}.get(key, "")
# Set only URL env var
with patch.dict(os.environ, {"HA_URL": "http://supervisor/core"}, clear=False):
os.environ.pop("HA_TOKEN", None)
result = await get_homeassistant_settings(db)
# URL from env, token from database
assert result["ha_url"] == "http://supervisor/core"
assert result["ha_token"] == "db-token"
assert result["ha_enabled"] is False # Not auto-enabled
assert result["ha_url_from_env"] is True
assert result["ha_token_from_env"] is False
assert result["ha_env_managed"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_partial_env_token_only():
"""Test get_homeassistant_settings with only HA_TOKEN set."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "false",
}.get(key, "")
# Set only token env var
with patch.dict(os.environ, {"HA_TOKEN": "env-token"}, clear=False):
os.environ.pop("HA_URL", None)
result = await get_homeassistant_settings(db)
# URL from database, token from env
assert result["ha_url"] == "http://db-url:8123"
assert result["ha_token"] == "env-token"
assert result["ha_enabled"] is False # Not auto-enabled
assert result["ha_url_from_env"] is False
assert result["ha_token_from_env"] is True
assert result["ha_env_managed"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_empty_env_vars():
"""Test get_homeassistant_settings with empty environment variables."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "false",
}.get(key, "")
# Set empty env vars
with patch.dict(os.environ, {"HA_URL": "", "HA_TOKEN": ""}, clear=False):
result = await get_homeassistant_settings(db)
# Empty env vars treated as not set, should use database values
assert result["ha_url"] == "http://db-url:8123"
assert result["ha_token"] == "db-token"
assert result["ha_enabled"] is False
assert result["ha_url_from_env"] is False
assert result["ha_token_from_env"] is False
assert result["ha_env_managed"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_auto_enable_logic():
"""Test auto-enable behavior with various configurations."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
# Database has ha_enabled=false
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "",
"ha_token": "",
"ha_enabled": "false",
}.get(key, "")
# Test 1: No env vars - use database enabled state
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("HA_URL", None)
os.environ.pop("HA_TOKEN", None)
result = await get_homeassistant_settings(db)
assert result["ha_enabled"] is False
# Test 2: Both env vars set - auto-enable
with patch.dict(os.environ, {"HA_URL": "http://test", "HA_TOKEN": "token"}, clear=False):
result = await get_homeassistant_settings(db)
assert result["ha_enabled"] is True
# Test 3: Only URL - use database enabled state
with patch.dict(os.environ, {"HA_URL": "http://test"}, clear=False):
os.environ.pop("HA_TOKEN", None)
result = await get_homeassistant_settings(db)
assert result["ha_enabled"] is False
# Test 4: Only token - use database enabled state
with patch.dict(os.environ, {"HA_TOKEN": "token"}, clear=False):
os.environ.pop("HA_URL", None)
result = await get_homeassistant_settings(db)
assert result["ha_enabled"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_homeassistant_settings_env_vars_override_enabled_true():
"""Test that env vars auto-enable even when database has ha_enabled=true."""
from backend.app.api.routes.settings import get_homeassistant_settings
db = AsyncMock(spec=AsyncSession)
with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting:
# Database has ha_enabled=true
mock_get_setting.side_effect = lambda db, key: {
"ha_url": "http://db-url:8123",
"ha_token": "db-token",
"ha_enabled": "true",
}.get(key, "")
# Both env vars set - should still be enabled
with patch.dict(os.environ, {"HA_URL": "http://supervisor/core", "HA_TOKEN": "env-token"}, clear=False):
result = await get_homeassistant_settings(db)
assert result["ha_enabled"] is True # Auto-enabled by env vars
assert result["ha_url"] == "http://supervisor/core"
assert result["ha_token"] == "env-token"
assert result["ha_env_managed"] is True