199 lines
5.5 KiB
Python
199 lines
5.5 KiB
Python
from collections.abc import Callable, Iterator
|
|
from datetime import UTC, datetime
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from httpx import AsyncClient
|
|
|
|
from app.api.deps import get_current_principal, get_reading_service
|
|
from app.core.principal import Principal
|
|
from app.core.roles import AccountKind, Role
|
|
from app.models.energy import Reading
|
|
from app.services.reading import FenetreInverseeError, FenetreTropLargeError
|
|
|
|
|
|
def principal(role: Role = Role.LECTEUR) -> Principal:
|
|
return Principal(
|
|
id=uuid4(),
|
|
email=f"{role.value}@enervision.fr",
|
|
role=role,
|
|
kind=AccountKind.HUMAIN,
|
|
must_change_password=False,
|
|
)
|
|
|
|
|
|
def reading(reading_id: int = 1, site_id: str = "site-1") -> Reading:
|
|
return Reading(
|
|
reading_id=reading_id,
|
|
site_id=site_id,
|
|
timestamp=datetime(2026, 9, 16, tzinfo=UTC),
|
|
source="api_current",
|
|
consumption_kw=42.5,
|
|
consumption_kwh=None,
|
|
consumption_euros=None,
|
|
voltage_v=230.0,
|
|
current_a=None,
|
|
power_factor=None,
|
|
temperature_celsius=None,
|
|
humidity_percent=None,
|
|
solar_irradiance_wm2=None,
|
|
is_working_hours=True,
|
|
data_quality="good",
|
|
null_reasons=None,
|
|
imputed_values=None,
|
|
imputation_method=None,
|
|
raw_data={},
|
|
)
|
|
|
|
|
|
class FauxService:
|
|
def __init__(self, leve: Exception | None = None) -> None:
|
|
self.reading = reading()
|
|
self.leve = leve
|
|
self.appels: list[tuple[str | None, str | None, str | None, int, int]] = []
|
|
|
|
async def list_history(
|
|
self,
|
|
*,
|
|
site_id: str | None = None,
|
|
start: datetime | None = None,
|
|
end: datetime | None = None,
|
|
limit: int,
|
|
offset: int,
|
|
) -> list[Reading]:
|
|
self.appels.append((site_id, start, end, limit, offset))
|
|
if self.leve is not None:
|
|
raise self.leve
|
|
return [self.reading]
|
|
|
|
|
|
@pytest.fixture
|
|
def lecteur_connecte(app: FastAPI) -> Iterator[None]:
|
|
app.dependency_overrides[get_current_principal] = lambda: principal()
|
|
yield
|
|
app.dependency_overrides.pop(get_current_principal, None)
|
|
|
|
|
|
@pytest.fixture
|
|
def servi(app: FastAPI, lecteur_connecte: None) -> Iterator[Callable[..., FauxService]]:
|
|
def installe(*, leve: Exception | None = None) -> FauxService:
|
|
service = FauxService(leve=leve)
|
|
app.dependency_overrides[get_reading_service] = lambda: service
|
|
return service
|
|
|
|
yield installe
|
|
app.dependency_overrides.pop(get_reading_service, None)
|
|
|
|
|
|
async def test_list_readings_returns_the_readings(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
servi()
|
|
|
|
response = await client.get("/api/v1/readings")
|
|
|
|
assert response.status_code == 200
|
|
corps = response.json()
|
|
assert corps == [
|
|
{
|
|
"reading_id": 1,
|
|
"site_id": "site-1",
|
|
"timestamp": "2026-09-16T00:00:00Z",
|
|
"source": "api_current",
|
|
"consumption_kw": 42.5,
|
|
"consumption_kwh": None,
|
|
"consumption_euros": None,
|
|
"voltage_v": 230.0,
|
|
"current_a": None,
|
|
"power_factor": None,
|
|
"temperature_celsius": None,
|
|
"humidity_percent": None,
|
|
"solar_irradiance_wm2": None,
|
|
"is_working_hours": True,
|
|
"data_quality": "good",
|
|
"null_reasons": None,
|
|
"imputed_values": None,
|
|
"imputation_method": None,
|
|
}
|
|
]
|
|
|
|
|
|
async def test_list_readings_transmits_the_filters_and_pagination(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
service = servi()
|
|
|
|
response = await client.get(
|
|
"/api/v1/readings",
|
|
params={
|
|
"site_id": "site-1",
|
|
"start": "2026-09-01T00:00:00Z",
|
|
"end": "2026-09-02T00:00:00Z",
|
|
"limit": 50,
|
|
"offset": 10,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert service.appels == [
|
|
(
|
|
"site-1",
|
|
datetime(2026, 9, 1, tzinfo=UTC),
|
|
datetime(2026, 9, 2, tzinfo=UTC),
|
|
50,
|
|
10,
|
|
)
|
|
]
|
|
|
|
|
|
async def test_list_readings_returns_400_when_the_window_is_inverted(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
servi(leve=FenetreInverseeError())
|
|
|
|
response = await client.get("/api/v1/readings")
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
async def test_list_readings_returns_400_when_the_window_is_too_large(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
servi(leve=FenetreTropLargeError())
|
|
|
|
response = await client.get("/api/v1/readings")
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
async def test_list_readings_returns_422_for_a_limit_above_the_maximum(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
servi()
|
|
|
|
response = await client.get("/api/v1/readings", params={"limit": 5000})
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
async def test_list_readings_returns_422_for_a_negative_offset(
|
|
servi: Callable[..., FauxService], client: AsyncClient
|
|
) -> None:
|
|
servi()
|
|
|
|
response = await client.get("/api/v1/readings", params={"offset": -1})
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
async def test_list_readings_returns_an_empty_list_when_there_is_nothing(
|
|
lecteur_connecte: None, fake_session: Callable[..., None], client: AsyncClient
|
|
) -> None:
|
|
fake_session(result=[])
|
|
|
|
response = await client.get("/api/v1/readings")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == []
|