ai-co2-estimation / tests /test_api.py
Cyril Dupland
V1
c0be4dd
Raw
History Blame Contribute Delete
3.33 kB
"""Integration tests for the /estimate and /health endpoints."""
from __future__ import annotations
from dataclasses import dataclass
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
# -- helpers to build fake ecologits responses (same as unit tests) --
@dataclass
class _RangeValue:
min: float
max: float
@dataclass
class _Metric:
value: _RangeValue
@dataclass
class _FakeImpactsOutput:
gwp: _Metric | None = None
energy: _Metric | None = None
warnings: list | None = None
errors: list | None = None
def _make_impact(gwp_min: float = 0.001, gwp_max: float = 0.003):
return _FakeImpactsOutput(
gwp=_Metric(value=_RangeValue(min=gwp_min, max=gwp_max)),
energy=_Metric(value=_RangeValue(min=0.01, max=0.02)),
warnings=[],
errors=[],
)
# -- tests --
def test_health(client: TestClient):
resp = client.get("/health")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
@patch("app.services.impact.llm_impacts")
def test_estimate_success(mock_llm_impacts, client: TestClient):
mock_llm_impacts.return_value = _make_impact()
resp = client.post("/estimate", json={
"provider": "openai",
"model": "gpt-4o",
"output_tokens": 500,
"request_latency": 1.2,
})
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "ok"
assert body["data"] is not None
assert body["data"]["gwp_avg_kgco2eq"] == pytest.approx(0.002)
assert body["data"]["gwp_gco2eq"] == pytest.approx(2.0)
assert body["data"]["equivalences"]["car_km"] > 0
assert body["data"]["equivalences"]["tgv_km"] > 0
@patch("app.services.impact.llm_impacts")
def test_estimate_unknown_model(mock_llm_impacts, client: TestClient):
"""Unknown provider/model returns an error status, not HTTP 500."""
mock_llm_impacts.return_value = _FakeImpactsOutput(
gwp=None, energy=None, warnings=[], errors=["Model not found"],
)
resp = client.post("/estimate", json={
"provider": "unknown",
"model": "no-model",
"output_tokens": 100,
"request_latency": 0.5,
})
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "error"
assert body["data"] is None
assert len(body["errors"]) > 0
def test_estimate_missing_fields(client: TestClient):
"""Missing required fields should return 422."""
resp = client.post("/estimate", json={"provider": "openai"})
assert resp.status_code == 422
def test_estimate_invalid_token_count(client: TestClient):
"""output_tokens must be > 0."""
resp = client.post("/estimate", json={
"provider": "openai",
"model": "gpt-4o",
"output_tokens": 0,
"request_latency": 1.0,
})
assert resp.status_code == 422
@patch("app.services.impact.llm_impacts")
def test_estimate_ecologits_crash(mock_llm_impacts, client: TestClient):
"""If ecologits raises, the API should return HTTP 500."""
mock_llm_impacts.side_effect = RuntimeError("unexpected")
resp = client.post("/estimate", json={
"provider": "openai",
"model": "gpt-4o",
"output_tokens": 500,
"request_latency": 1.0,
})
assert resp.status_code == 500