"""Lab commands: diff, apply_from_master, recalculate.""" from __future__ import annotations import json import unittest from app import create_app, db from app.lab.calc.diff import compare_master_execution from app.lab.commands import apply_from_master, recalculate_ration, seed_from_execution from app.lab.models import LabRationLine, LabRecipeRation from app.lab.services.component_nutrients import upsert_from_api_dict from app.models import Component, Ingredient, Recipe from app.models.base import default_uuid from app.services.setup_state import mark_setup_complete from tests.helpers.zootech_test_helpers import ZootechTestConfig class LabCommandsTests(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.comp = Component( id=default_uuid(), name="Сено", type="Объемные корма", dry_matter=85.0, protein=10.0, energy=8.0, price=5.0, ) db.session.add(self.comp) db.session.flush() upsert_from_api_dict(self.comp.id, {"Сыр. Протеин": 100, "СВ": 850}) self.recipe = Recipe( id=default_uuid(), name="Lab cmd test", heads_per_trip=10, mixing_time=5, ration_type="BEEF", ) self.ingredient = Ingredient( id=default_uuid(), recipe_id=self.recipe.id, component_id=self.comp.id, name="Сено", amount=5.0, weight_per_head=5.0, dry_matter=85.0, order=0, ) db.session.add_all([self.recipe, self.ingredient]) db.session.commit() self.recipe_id = self.recipe.id self.client.post( "/api/auth/login", json={"login": "zootech-test-admin", "password": "zootech-test-secret"}, ) def tearDown(self) -> None: db.session.remove() db.drop_all() self.ctx.pop() def test_master_diff_detects_kg_mismatch(self) -> None: seed_from_execution(self.recipe_id) header = LabRecipeRation.query.filter_by(recipe_id=self.recipe_id).first() line = LabRationLine.query.filter_by(recipe_id=self.recipe_id).first() line.daily_kg = 60.0 db.session.commit() master_lines = [ {"component_id": self.comp.id, "daily_kg": 60.0, "in_ration": True}, ] exec_lines = [ {"component_id": self.comp.id, "daily_kg_total": 50.0}, ] diff = compare_master_execution(master_lines, exec_lines) self.assertTrue(diff["has_changes"]) self.assertEqual(diff["lines"][0]["reasons"], ["kg_mismatch"]) def test_apply_from_master_updates_ingredient(self) -> None: seed_from_execution(self.recipe_id) line = LabRationLine.query.filter_by(recipe_id=self.recipe_id).first() line.daily_kg = 30.0 db.session.commit() result = apply_from_master(self.recipe_id) self.assertEqual(result["recipeId"], self.recipe_id) db.session.refresh(self.ingredient) self.assertAlmostEqual(float(self.ingredient.weight_per_head), 3.0, places=2) def test_recalculate_returns_indicators(self) -> None: seed_from_execution(self.recipe_id) result = recalculate_ration(self.recipe_id) self.assertIn("indicators", result) self.assertTrue(result["indicators"]) def test_diff_api_after_seed(self) -> None: self.client.post(f"/api/lab/rations/{self.recipe_id}/seed-from-execution") r = self.client.get(f"/api/lab/rations/{self.recipe_id}/diff") self.assertEqual(r.status_code, 200) body = r.get_json() self.assertIn("hasChanges", body)