"""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