first
This commit is contained in:
0
backend/tests/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
39
backend/tests/conftest.py
Normal file
39
backend/tests/conftest.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlmodel import Session, SQLModel, create_engine
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.models import EntityCache, EntityFlag, AuditLog
|
||||
from app.database import set_engine, get_session
|
||||
|
||||
|
||||
@pytest.fixture(name="engine")
|
||||
def engine_fixture():
|
||||
engine = create_engine(
|
||||
"sqlite:///:memory:",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
set_engine(engine)
|
||||
yield engine
|
||||
|
||||
|
||||
@pytest.fixture(name="session")
|
||||
def session_fixture(engine):
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def client_fixture(engine):
|
||||
from app.main import app
|
||||
|
||||
def override_get_session():
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
app.dependency_overrides[get_session] = override_get_session
|
||||
client = TestClient(app)
|
||||
yield client
|
||||
app.dependency_overrides.clear()
|
||||
80
backend/tests/test_actions.py
Normal file
80
backend/tests/test_actions.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.models import EntityCache, EntityFlag, AuditLog
|
||||
|
||||
|
||||
def _seed_entity(session: Session):
|
||||
session.add(EntityCache(
|
||||
entity_id="light.test",
|
||||
domain="light",
|
||||
friendly_name="Test",
|
||||
state="on",
|
||||
fetched_at=datetime.utcnow(),
|
||||
))
|
||||
session.commit()
|
||||
|
||||
|
||||
def test_favorite_entity(client, session):
|
||||
_seed_entity(session)
|
||||
resp = client.post("/api/entities/actions", json={
|
||||
"action": "favorite",
|
||||
"entity_ids": ["light.test"],
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["action"] == "favorite"
|
||||
assert data["results"][0]["ok"] is True
|
||||
|
||||
# Vérifier le flag
|
||||
flag = session.get(EntityFlag, "light.test")
|
||||
assert flag is not None
|
||||
assert flag.favorite is True
|
||||
|
||||
|
||||
def test_unfavorite_entity(client, session):
|
||||
_seed_entity(session)
|
||||
# D'abord favori
|
||||
client.post("/api/entities/actions", json={
|
||||
"action": "favorite",
|
||||
"entity_ids": ["light.test"],
|
||||
})
|
||||
# Puis défavori
|
||||
resp = client.post("/api/entities/actions", json={
|
||||
"action": "unfavorite",
|
||||
"entity_ids": ["light.test"],
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
flag = session.get(EntityFlag, "light.test")
|
||||
assert flag.favorite is False
|
||||
|
||||
|
||||
def test_ignore_entity(client, session):
|
||||
_seed_entity(session)
|
||||
resp = client.post("/api/entities/actions", json={
|
||||
"action": "ignore",
|
||||
"entity_ids": ["light.test"],
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
flag = session.get(EntityFlag, "light.test")
|
||||
assert flag.ignored_local is True
|
||||
|
||||
|
||||
def test_bulk_action(client, session):
|
||||
session.add(EntityCache(entity_id="light.a", domain="light", state="on", fetched_at=datetime.utcnow()))
|
||||
session.add(EntityCache(entity_id="light.b", domain="light", state="off", fetched_at=datetime.utcnow()))
|
||||
session.commit()
|
||||
|
||||
resp = client.post("/api/entities/actions", json={
|
||||
"action": "favorite",
|
||||
"entity_ids": ["light.a", "light.b"],
|
||||
})
|
||||
data = resp.json()
|
||||
assert len(data["results"]) == 2
|
||||
|
||||
# Vérifier audit_log
|
||||
from sqlmodel import select
|
||||
logs = session.exec(select(AuditLog)).all()
|
||||
assert len(logs) >= 1
|
||||
assert logs[-1].action == "favorite"
|
||||
119
backend/tests/test_entities.py
Normal file
119
backend/tests/test_entities.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.models import EntityCache, EntityFlag
|
||||
|
||||
|
||||
def _seed_entities(session: Session):
|
||||
entities = [
|
||||
EntityCache(
|
||||
entity_id="light.salon",
|
||||
domain="light",
|
||||
friendly_name="Lumière Salon",
|
||||
state="on",
|
||||
is_available=True,
|
||||
fetched_at=datetime.utcnow(),
|
||||
),
|
||||
EntityCache(
|
||||
entity_id="sensor.temperature",
|
||||
domain="sensor",
|
||||
friendly_name="Température",
|
||||
state="22.5",
|
||||
device_class="temperature",
|
||||
unit_of_measurement="°C",
|
||||
is_available=True,
|
||||
fetched_at=datetime.utcnow(),
|
||||
),
|
||||
EntityCache(
|
||||
entity_id="switch.garage",
|
||||
domain="switch",
|
||||
friendly_name="Garage",
|
||||
state="off",
|
||||
is_available=True,
|
||||
fetched_at=datetime.utcnow(),
|
||||
),
|
||||
EntityCache(
|
||||
entity_id="sensor.humidity",
|
||||
domain="sensor",
|
||||
friendly_name="Humidité",
|
||||
state="unavailable",
|
||||
is_available=False,
|
||||
fetched_at=datetime.utcnow(),
|
||||
),
|
||||
]
|
||||
for e in entities:
|
||||
session.add(e)
|
||||
session.commit()
|
||||
|
||||
|
||||
def test_list_entities_empty(client):
|
||||
resp = client.get("/api/entities")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["items"] == []
|
||||
assert data["total"] == 0
|
||||
|
||||
|
||||
def test_list_entities_with_data(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 4
|
||||
assert len(data["items"]) == 4
|
||||
|
||||
|
||||
def test_list_entities_filter_domain(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities?domain=sensor")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 2
|
||||
assert all(e["domain"] == "sensor" for e in data["items"])
|
||||
|
||||
|
||||
def test_list_entities_filter_multi_domain(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities?domain=light,switch")
|
||||
data = resp.json()
|
||||
assert data["total"] == 2
|
||||
|
||||
|
||||
def test_list_entities_search(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities?search=salon")
|
||||
data = resp.json()
|
||||
assert data["total"] == 1
|
||||
assert data["items"][0]["entity_id"] == "light.salon"
|
||||
|
||||
|
||||
def test_list_entities_filter_available(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities?available=false")
|
||||
data = resp.json()
|
||||
assert data["total"] == 1
|
||||
assert data["items"][0]["entity_id"] == "sensor.humidity"
|
||||
|
||||
|
||||
def test_list_entities_pagination(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities?page=1&per_page=2")
|
||||
data = resp.json()
|
||||
assert len(data["items"]) == 2
|
||||
assert data["total"] == 4
|
||||
assert data["pages"] == 2
|
||||
|
||||
|
||||
def test_get_entity_detail(client, session):
|
||||
_seed_entities(session)
|
||||
resp = client.get("/api/entities/light.salon")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["entity_id"] == "light.salon"
|
||||
assert data["favorite"] is False
|
||||
|
||||
|
||||
def test_get_entity_not_found(client):
|
||||
resp = client.get("/api/entities/nonexistent.entity")
|
||||
assert resp.status_code == 404
|
||||
79
backend/tests/test_ha_client.py
Normal file
79
backend/tests/test_ha_client.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from app.ha_client import normalize_entity
|
||||
|
||||
|
||||
def test_normalize_entity_basic():
|
||||
state = {
|
||||
"entity_id": "light.salon",
|
||||
"state": "on",
|
||||
"attributes": {
|
||||
"friendly_name": "Lumière Salon",
|
||||
"device_class": "light",
|
||||
},
|
||||
"last_changed": "2026-01-01T00:00:00Z",
|
||||
"last_updated": "2026-01-01T00:00:00Z",
|
||||
}
|
||||
result = normalize_entity(state)
|
||||
|
||||
assert result["entity_id"] == "light.salon"
|
||||
assert result["domain"] == "light"
|
||||
assert result["friendly_name"] == "Lumière Salon"
|
||||
assert result["state"] == "on"
|
||||
assert result["device_class"] == "light"
|
||||
assert result["is_available"] is True
|
||||
assert result["is_disabled"] is False
|
||||
|
||||
|
||||
def test_normalize_entity_unavailable():
|
||||
state = {
|
||||
"entity_id": "sensor.temp",
|
||||
"state": "unavailable",
|
||||
"attributes": {},
|
||||
}
|
||||
result = normalize_entity(state)
|
||||
|
||||
assert result["is_available"] is False
|
||||
assert result["domain"] == "sensor"
|
||||
|
||||
|
||||
def test_normalize_entity_with_registry():
|
||||
state = {
|
||||
"entity_id": "switch.garage",
|
||||
"state": "off",
|
||||
"attributes": {"friendly_name": "Garage"},
|
||||
}
|
||||
registry = {
|
||||
"entity_id": "switch.garage",
|
||||
"area_id": "garage",
|
||||
"device_id": "dev_123",
|
||||
"platform": "esphome",
|
||||
"disabled_by": None,
|
||||
"hidden_by": "user",
|
||||
}
|
||||
result = normalize_entity(state, registry)
|
||||
|
||||
assert result["area_id"] == "garage"
|
||||
assert result["device_id"] == "dev_123"
|
||||
assert result["integration"] == "esphome"
|
||||
assert result["is_disabled"] is False
|
||||
assert result["is_hidden"] is True
|
||||
|
||||
|
||||
def test_normalize_entity_disabled_in_registry():
|
||||
state = {
|
||||
"entity_id": "sensor.disabled",
|
||||
"state": "unknown",
|
||||
"attributes": {},
|
||||
}
|
||||
registry = {
|
||||
"entity_id": "sensor.disabled",
|
||||
"disabled_by": "user",
|
||||
"hidden_by": None,
|
||||
"area_id": None,
|
||||
"device_id": None,
|
||||
"platform": "mqtt",
|
||||
}
|
||||
result = normalize_entity(state, registry)
|
||||
|
||||
assert result["is_disabled"] is True
|
||||
assert result["is_available"] is False
|
||||
assert result["integration"] == "mqtt"
|
||||
Reference in New Issue
Block a user