Files
site/apps/api/tests/modules/zootech/test_wesp_compat.py
T

357 lines
12 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime
from uuid import uuid4
import pytest
from fastapi.testclient import TestClient
from app.modules.zootech.catalog_apply import apply_catalog_change
from tests.orchestrator_dual_harness import make_enterprise
def test_wesp_compat_feed_dispensers(client: TestClient):
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
resp = client.get(f"/api/feed_dispensers?enterprise_id={enterprise_id}", headers=headers)
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_wesp_compat_components(client: TestClient):
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
resp = client.get(f"/api/components?enterprise_id={enterprise_id}", headers=headers)
assert resp.status_code == 200
assert isinstance(resp.json(), list)
def test_wesp_compat_recipe_includes_ingredients_and_unloading_groups(client: TestClient):
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
recipe_id = "recipe-compat-1"
ing_id = "ing-compat-1"
grp_id = "grp-compat-1"
apply_catalog_change(
enterprise_id,
"recipe",
recipe_id,
"upsert",
{
"id": recipe_id,
"name": "Compat Mix",
"heads_per_trip": 100,
"mixing_time": 10,
"trip_percent": 100.0,
},
1,
"r1",
)
apply_catalog_change(
enterprise_id,
"ingredient",
ing_id,
"upsert",
{
"id": ing_id,
"recipe_id": recipe_id,
"name": "Corn",
"amount": 12.5,
"order": 1,
"dry_matter": 88.0,
},
1,
"i1",
)
apply_catalog_change(
enterprise_id,
"unloading_group",
grp_id,
"upsert",
{
"id": grp_id,
"recipe_id": recipe_id,
"name": "Barn A",
"distribution_type": "percent",
"value": 50.0,
"weight": 500.0,
"order": 1,
},
1,
"g1",
)
resp = client.get(f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}", headers=headers)
assert resp.status_code == 200
body = resp.json()
assert body["name"] == "Compat Mix"
assert len(body.get("ingredients") or []) == 1
assert body["ingredients"][0]["name"] == "Corn"
assert len(body.get("unloading_groups") or body.get("unloadingGroups") or []) == 1
assert body["unloading_groups"][0]["name"] == "Barn A"
def test_wesp_compat_recipe_calculate(client: TestClient):
resp = client.post(
"/api/recipes/calculate",
json={
"ingredients": [{"weightPerHead": 10, "dryMatter": 88, "component_id": "c1"}],
"headsCount": 100,
"tripPercent": 100,
"unloadingGroups": [{"distributionType": "percent", "value": 50}],
},
)
assert resp.status_code == 200
body = resp.json()
assert len(body.get("ingredients") or []) == 1
assert len(body.get("unloadingGroups") or []) == 1
def test_wesp_compat_recipe_put_persists_unloading_group(client: TestClient):
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
recipe_id = "recipe-put-1"
apply_catalog_change(
enterprise_id,
"recipe",
recipe_id,
"upsert",
{"id": recipe_id, "name": "Before", "heads_per_trip": 100, "mixing_time": 10, "trip_percent": 100.0},
1,
"r1",
)
put = client.put(
f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}",
headers=headers,
json={
"name": "After",
"heads_count": 100,
"mixing_time": 10,
"trip_percent": 100,
"dry_matter_locked": False,
"unloading_link_broken": False,
"ingredients": [],
"unloading_groups": [
{
"name": "Group A",
"distribution_type": "percent",
"value": 100,
"weight": 500,
"order": 1,
}
],
},
)
assert put.status_code == 200
got = client.get(f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}", headers=headers)
assert got.status_code == 200
groups = got.json().get("unloading_groups") or []
assert len(groups) == 1
assert groups[0]["name"] == "Group A"
def test_wesp_compat_analytics_endpoints(client: TestClient):
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
params = f"enterprise_id={enterprise_id}&date_from=2026-07-01&date_to=2026-07-15"
finance = client.get(f"/api/analytics/finance?{params}", headers=headers)
assert finance.status_code == 200
assert "overloadRub" in finance.json()
plan_fact = client.get(f"/api/analytics/plan-fact?{params}", headers=headers)
assert plan_fact.status_code == 200
assert plan_fact.json()["items"] == []
summary = client.get(f"/api/feed-quality/alerts/summary?{params}", headers=headers)
assert summary.status_code == 200
assert "total" in summary.json()
alerts = client.get(f"/api/feed-quality/alerts?{params}", headers=headers)
assert alerts.status_code == 200
assert "items" in alerts.json()
def test_wesp_compat_reports_list(client: TestClient):
from app.modules.zootech.report_apply import apply_report_change
enterprise_id = make_enterprise(client)
login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"})
token = login.json()["access_token"]
session = client.post(
f"/api/v1/enterprise/enterprises/{enterprise_id}/session",
headers={"Authorization": f"Bearer {token}"},
)
token = session.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
loading_id = "lr-test-1"
apply_report_change(
enterprise_id,
"loading_report",
loading_id,
"upsert",
{
"recipe_id": "recipe-1",
"recipe_name": "Test Mix",
"start_time": datetime.now(UTC).isoformat(),
"end_time": datetime.now(UTC).isoformat(),
"total_weight": 1200.0,
"dispenser_type": "dispenser",
"components": [{"component_name": "Corn", "actual_weight": 100.0}],
},
1,
"h1",
)
apply_report_change(
enterprise_id,
"unloading_report",
"ur-test-1",
"upsert",
{
"loading_report_id": loading_id,
"start_time": datetime.now(UTC).isoformat(),
"total_weight": 1200.0,
"total_unloaded_weight": 1100.0,
"remaining_weight": 100.0,
},
1,
"u1",
)
resp = client.get(f"/api/reports?enterprise_id={enterprise_id}", headers=headers)
assert resp.status_code == 200
body = resp.json()
assert isinstance(body, list)
assert len(body) == 1
assert body[0]["recipe_id"] == "recipe-1"
assert body[0]["unloading_data"]["total_unloaded_weight"] == 1100.0
def test_apply_report_change_update_adds_components(client: TestClient):
import json
from app.core.database import session_scope
from app.modules.zootech.report_apply import apply_report_change
from app.modules.zootech.report_models import ZootechLoadingReport
from sqlalchemy import select
enterprise_id = make_enterprise(client)
loading_id = "lr-test-components"
apply_report_change(
enterprise_id,
"loading_report",
loading_id,
"upsert",
{
"recipe_id": "recipe-1",
"recipe_name": "Test Mix",
"start_time": datetime.now(UTC).isoformat(),
"total_weight": 1200.0,
},
1,
"h1",
)
apply_report_change(
enterprise_id,
"loading_report",
loading_id,
"upsert",
{
"recipe_id": "recipe-1",
"recipe_name": "Test Mix",
"start_time": datetime.now(UTC).isoformat(),
"total_weight": 1200.0,
"components": [{"component_name": "Corn", "actual_weight": 100.0, "overload": 5.0}],
},
2,
"h2",
)
with session_scope() as db:
row = db.scalar(
select(ZootechLoadingReport).where(
ZootechLoadingReport.enterprise_id == enterprise_id,
ZootechLoadingReport.id == loading_id,
)
)
assert row is not None
payload = json.loads(row.payload_json or "{}")
assert len(payload.get("components") or []) == 1
assert payload["components"][0]["component_name"] == "Corn"
def test_apply_report_change_feed_alert(client: TestClient):
from app.core.database import session_scope
from app.modules.zootech.report_apply import apply_report_change
from app.modules.zootech.report_models import ZootechFeedAlert
from sqlalchemy import select
enterprise_id = make_enterprise(client)
alert_id = "fa-test-1"
apply_report_change(
enterprise_id,
"feed_alert",
alert_id,
"upsert",
{
"event_type": "UNDERLOAD",
"severity": "error",
"loading_report_id": "lr-1",
"recipe_id": "recipe-1",
"recipe_name": "Mix",
"detail": "Underload detected",
"deviation_kg": -5.0,
},
1,
"h1",
)
with session_scope() as db:
row = db.scalar(
select(ZootechFeedAlert).where(
ZootechFeedAlert.enterprise_id == enterprise_id,
ZootechFeedAlert.id == alert_id,
)
)
assert row is not None
assert row.alert_type == "UNDERLOAD"
assert row.message == "Underload detected"
assert row.payload_json