Files
site/WESP_REL/tests/test_analytics_api.py
2026-07-17 12:57:18 +03:00

187 lines
6.8 KiB
Python

"""API /api/analytics."""
from __future__ import annotations
import unittest
from datetime import datetime
from app import create_app, db
from app.models import Component, LoadingReport, LoadingReportComponent, Recipe
from app.services.setup_state import mark_setup_complete
from tests.helpers.zootech_test_helpers import ZootechTestConfig
class AnalyticsApiTests(unittest.TestCase):
def setUp(self) -> None:
self.app = create_app(ZootechTestConfig)
self.client = self.app.test_client()
self.ctx = self.app.app_context()
self.ctx.push()
db.create_all()
mark_setup_complete(self.app)
self.client.post(
"/api/auth/login",
json={"login": "zootech-test-admin", "password": "zootech-test-secret"},
)
db.session.add(
Component(
id="api-an-c1",
name="Силос",
type="forage",
dry_matter=30.0,
protein=0.0,
energy=0.0,
price=3.0,
)
)
db.session.add(
Recipe(id="api-an-r1", name="Рейс", heads_per_trip=1, content_hash="")
)
report = LoadingReport(
id="api-an-lr",
recipe_id="api-an-r1",
recipe_name="Рейс",
start_time=datetime.now(),
target_mixing_time=60,
actual_mixing_time=60,
total_weight=100.0,
created_by="system",
updated_by="system",
)
db.session.add(report)
db.session.flush()
db.session.add(
LoadingReportComponent(
report_id=report.id,
component_id="api-an-c1",
component_name="Силос",
target_weight=100.0,
actual_weight=150.0,
overload=50.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
self.today = datetime.now().strftime("%Y-%m-%d")
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
self.ctx.pop()
def test_finance_endpoint(self) -> None:
resp = self.client.get(
f"/api/analytics/finance?date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
body = resp.get_json()
self.assertEqual(body["overloadRub"], 150.0)
self.assertIn("dominantIssue", body)
self.assertEqual(body["dominantIssue"], "overload")
def test_plan_fact_endpoint(self) -> None:
resp = self.client.get(
f"/api/analytics/plan-fact?date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
body = resp.get_json()
self.assertEqual(body["reportCount"], 1)
self.assertEqual(body["items"][0]["loadingReportId"], "api-an-lr")
def test_stock_forecast_endpoint(self) -> None:
resp = self.client.get("/api/analytics/stock-forecast")
self.assertEqual(resp.status_code, 200)
body = resp.get_json()
self.assertIn("items", body)
self.assertIn("alertBanner", body)
def test_finance_export_three_sheets(self) -> None:
resp = self.client.get(
f"/api/analytics/finance/export?date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
from openpyxl import load_workbook
import io
wb = load_workbook(io.BytesIO(resp.data))
self.assertEqual(set(wb.sheetnames), {"Итоги", "Сравнение", "Подробно"})
def test_analytics_export_xlsx(self) -> None:
resp = self.client.get(
f"/api/analytics/export?format=xlsx&date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
self.assertIn("spreadsheetml", resp.content_type or resp.mimetype or "")
def test_analytics_export_comparison_sheet_layout(self) -> None:
resp = self.client.get(
f"/api/analytics/export?format=xlsx&section=comparison&date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
import io
wb = load_workbook(io.BytesIO(resp.data))
self.assertEqual(wb.sheetnames, ["Сравнение"])
ws = wb["Сравнение"]
headers = [cell.value for cell in ws[1]]
self.assertIn("Разница План/Факт, кг", headers)
self.assertNotIn("Факс", "".join(str(h) for h in headers if h))
for header in (
"По рецепту (База), кг",
"На сегодня (План), кг",
"Разница База/Факт, кг",
"Разница План/Факт, кг",
):
col_idx = headers.index(header) + 1
width = ws.column_dimensions[get_column_letter(col_idx)].width
self.assertGreaterEqual(width, 18, msg=f"Column {header} too narrow: {width}")
self.assertGreater(len(ws.conditional_formatting), 0)
def test_analytics_export_pdf(self) -> None:
resp = self.client.get(
f"/api/analytics/export?format=pdf&date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.content_type, "application/pdf")
self.assertTrue(resp.data.startswith(b"%PDF"))
def test_analytics_export_summary_only_xlsx(self) -> None:
resp = self.client.get(
f"/api/analytics/export?format=xlsx&section=summary&date_from={self.today}&date_to={self.today}"
)
self.assertEqual(resp.status_code, 200)
from openpyxl import load_workbook
import io
wb = load_workbook(io.BytesIO(resp.data))
self.assertEqual(wb.sheetnames, ["Итоги"])
ws = wb["Итоги"]
header_row = next(
row[0].row
for row in ws.iter_rows(min_row=1, max_col=1)
if row[0].value == "Ферма"
)
header_values = [cell.value for cell in ws[header_row]]
self.assertIn("Ферма", header_values)
self.assertIn("Перерасход, ₽", header_values)
net_row = next(
row[0].row
for row in ws.iter_rows(min_row=1, max_col=1)
if row[0].value == "Итого по деньгам, ₽"
)
net_value = ws.cell(row=net_row, column=2).value
self.assertGreaterEqual(float(net_value or 0), 0)
hint = ws.cell(row=net_row + 1, column=1).value
self.assertIn("перерасход", str(hint).lower())
if __name__ == "__main__":
unittest.main()