"""Порядок рецептов в периоде и reorder ингредиентов/групп.""" from __future__ import annotations import os import tempfile import unittest import time from datetime import datetime from sqlalchemy import select from app import create_app, db from app.models import ( FeedDispenser, FeedingPeriod, Ingredient, PeriodRecipe, Recipe, SyncQueue, UnloadingGroup, ) from app.services.recipe_update_service import update_recipe_from_payload from config import TestingConfig from tests.helpers.mill_recipe_fixtures import ( create_recipe_with_children, group_payload_from_row, ingredient_payload_from_row, recipe_update_payload, ) class RecipesOrderConfig(TestingConfig): _TMP_DIR = tempfile.mkdtemp(prefix="wesp-recipes-order-") SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(_TMP_DIR, 'recipes_test.db')}" SQLALCHEMY_BINDS = {"reports": f"sqlite:///{os.path.join(_TMP_DIR, 'reports_test.db')}"} AUTH_LOGIN = "order-admin" AUTH_PASSWORD = "order-secret" class RecipesOrderTests(unittest.TestCase): def setUp(self) -> None: self.app = create_app(RecipesOrderConfig) self.client = self.app.test_client() self.ctx = self.app.app_context() self.ctx.push() db.create_all() self.client.post( "/api/auth/login", json={"login": "order-admin", "password": "order-secret"} ) def tearDown(self) -> None: db.session.remove() db.drop_all() self.ctx.pop() def _seed_period_two_recipes(self) -> tuple[str, str, str, str]: disp = FeedDispenser( id="ord-disp", name="D", farm="F", operator="O", type="dispenser" ) period = FeedingPeriod(id="ord-period", name="P", dispenser_id=disp.id) r1 = Recipe(id="ord-r1", name="R1", heads_per_trip=1, mixing_time=0) r2 = Recipe(id="ord-r2", name="R2", heads_per_trip=1, mixing_time=0) db.session.add_all([disp, period, r1, r2]) db.session.flush() now = datetime.now() for rid, ord_ in ((r1.id, 1), (r2.id, 2)): db.session.add( PeriodRecipe( period_id=period.id, recipe_id=rid, order=ord_, created_at=now, created_by="system", updated_by="system", ) ) db.session.commit() return disp.id, period.id, r1.id, r2.id def _period_order(self, period_id: str) -> list[str]: rows = db.session.execute( select(PeriodRecipe.recipe_id) .where( PeriodRecipe.period_id == period_id, PeriodRecipe.is_deleted.is_(False), ) .order_by(PeriodRecipe.order.asc()) ).scalars().all() return [str(x) for x in rows] def test_move_recipe_down_in_period(self) -> None: _, period_id, r1, r2 = self._seed_period_two_recipes() resp = self.client.put( f"/api/recipes/{r1}/move", json={"period_id": period_id, "from_index": 0, "to_index": 1}, ) self.assertEqual(resp.status_code, 200) self.assertEqual(self._period_order(period_id), [r2, r1]) def test_move_recipe_up_in_period(self) -> None: _, period_id, r1, r2 = self._seed_period_two_recipes() self.client.put( f"/api/recipes/{r2}/move", json={"period_id": period_id, "from_index": 1, "to_index": 0}, ) self.assertEqual(self._period_order(period_id), [r2, r1]) def test_move_recipe_sync_enqueue_period_recipes(self) -> None: _, period_id, r1, r2 = self._seed_period_two_recipes() self.client.put( f"/api/recipes/{r1}/move", json={"period_id": period_id, "from_index": 0, "to_index": 1}, ) time.sleep(0.3) tasks = db.session.execute( select(SyncQueue).where(SyncQueue.table_name == "period_recipes") ).scalars().all() self.assertGreaterEqual(len(tasks), 1) record_ids = {t.record_id for t in tasks} self.assertTrue(f"{period_id}:{r1}" in record_ids or f"{period_id}:{r2}" in record_ids) def test_move_recipe_404_wrong_period(self) -> None: _, period_id, r1, _ = self._seed_period_two_recipes() other = FeedingPeriod(id="ord-other", name="X", dispenser_id="ord-disp") db.session.add(other) db.session.commit() resp = self.client.put( f"/api/recipes/{r1}/move", json={"period_id": other.id, "from_index": 0, "to_index": 1}, ) self.assertEqual(resp.status_code, 404) def test_update_reorders_ingredients_by_id(self) -> None: recipe_id, ing_ids, _ = create_recipe_with_children(with_groups=False) recipe = db.session.get(Recipe, recipe_id) ing_a = db.session.get(Ingredient, ing_ids[0]) ing_b = db.session.get(Ingredient, ing_ids[1]) update_recipe_from_payload( recipe_id, recipe_update_payload( recipe, ingredients=[ ingredient_payload_from_row(ing_b, order=1), ingredient_payload_from_row(ing_a, order=2), ], groups=[], ), ) db.session.commit() orders = db.session.execute( select(Ingredient.id, Ingredient.order) .where(Ingredient.recipe_id == recipe_id, Ingredient.is_deleted.is_(False)) .order_by(Ingredient.order.asc()) ).all() self.assertEqual([str(x[0]) for x in orders], [ing_ids[1], ing_ids[0]]) def test_move_ingredient_up_down_reflected_in_save(self) -> None: recipe_id, ing_ids, _ = create_recipe_with_children(with_groups=False) recipe = db.session.get(Recipe, recipe_id) ing_a = db.session.get(Ingredient, ing_ids[0]) ing_b = db.session.get(Ingredient, ing_ids[1]) payload = recipe_update_payload( recipe, ingredients=[ ingredient_payload_from_row(ing_b, order=1), ingredient_payload_from_row(ing_a, order=2), ], groups=[], ) update_recipe_from_payload(recipe_id, payload) db.session.commit() rows = db.session.execute( select(Ingredient.order).where( Ingredient.recipe_id == recipe_id, Ingredient.is_deleted.is_(False) ).order_by(Ingredient.order.asc()) ).scalars().all() self.assertEqual(list(rows), [1, 2]) def test_move_group_up_down_deleted_order_stable(self) -> None: recipe_id, _, grp_ids = create_recipe_with_children() recipe = db.session.get(Recipe, recipe_id) g1 = db.session.get(UnloadingGroup, grp_ids[0]) g2 = db.session.get(UnloadingGroup, grp_ids[1]) update_recipe_from_payload( recipe_id, recipe_update_payload( recipe, ingredients=[], groups=[ group_payload_from_row(g2, order=1), group_payload_from_row(g1, order=2), ], ), ) db.session.commit() orders = db.session.execute( select(UnloadingGroup.id, UnloadingGroup.order) .where(UnloadingGroup.recipe_id == recipe_id, UnloadingGroup.is_deleted.is_(False)) .order_by(UnloadingGroup.order.asc()) ).all() self.assertEqual([str(x[0]) for x in orders], [grp_ids[1], grp_ids[0]]) if __name__ == "__main__": unittest.main()