240 lines
9.0 KiB
Python
240 lines
9.0 KiB
Python
"""Dual sync: reorder ингредиентов + группы выгрузки (баготест #5–#6)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import unittest
|
||
import uuid
|
||
|
||
from sqlalchemy import select
|
||
|
||
from app import db
|
||
from app.models import Ingredient, Recipe, UnloadingGroup
|
||
from app.services.recipe_update_service import update_recipe_from_payload
|
||
from app.services.sync_manager import enqueue_sync_queue_task
|
||
from tests.helpers.mill_recipe_fixtures import (
|
||
create_recipe_with_children,
|
||
group_payload_from_row,
|
||
ingredient_payload_from_row,
|
||
recipe_update_payload,
|
||
)
|
||
from tests.helpers.sync_dual_harness import SyncDualInstanceHarness
|
||
|
||
SCENARIO_DUAL_REORDER_AND_NEW_GROUP = "DUAL_REORDER_AND_NEW_GROUP"
|
||
SCENARIO_DUAL_ADD_GROUP_UPDATES_EXISTING = "DUAL_ADD_GROUP_UPDATES_EXISTING"
|
||
|
||
|
||
class SyncRecipeChildrenDualTests(unittest.TestCase):
|
||
def setUp(self) -> None:
|
||
self.harness = SyncDualInstanceHarness()
|
||
self.harness.start()
|
||
|
||
def tearDown(self) -> None:
|
||
self.harness.stop()
|
||
|
||
def _seed_recipe_on_terminals(self, recipe_id: str) -> None:
|
||
with self.harness.server_ctx():
|
||
enqueue_sync_queue_task("recipe", recipe_id, "create", priority=2, target_node_id=None)
|
||
for ing in db.session.execute(
|
||
select(Ingredient).where(
|
||
Ingredient.recipe_id == recipe_id,
|
||
Ingredient.is_deleted.is_(False),
|
||
)
|
||
).scalars().all():
|
||
enqueue_sync_queue_task("ingredient", str(ing.id), "create", priority=2)
|
||
for grp in db.session.execute(
|
||
select(UnloadingGroup).where(
|
||
UnloadingGroup.recipe_id == recipe_id,
|
||
UnloadingGroup.is_deleted.is_(False),
|
||
)
|
||
).scalars().all():
|
||
enqueue_sync_queue_task("unloading_group", str(grp.id), "create", priority=2)
|
||
db.session.commit()
|
||
self.harness.drain_sync()
|
||
|
||
def _ingredient_orders(self, which: str, recipe_id: str) -> list[tuple[str, int]]:
|
||
with self.harness.terminal_ctx(which):
|
||
rows = 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()
|
||
return [(str(rid), int(ord_)) for rid, ord_ in rows]
|
||
|
||
def _group_snapshot(self, which: str, recipe_id: str) -> list[tuple[str, str, float]]:
|
||
with self.harness.terminal_ctx(which):
|
||
rows = db.session.execute(
|
||
select(
|
||
UnloadingGroup.name,
|
||
UnloadingGroup.distribution_type,
|
||
UnloadingGroup.value,
|
||
)
|
||
.where(
|
||
UnloadingGroup.recipe_id == recipe_id,
|
||
UnloadingGroup.is_deleted.is_(False),
|
||
)
|
||
.order_by(UnloadingGroup.order.asc())
|
||
).all()
|
||
return [(n, t, float(v)) for n, t, v in rows]
|
||
|
||
def test_reorder_ingredients_and_add_group_reach_both_terminals(self) -> None:
|
||
SCENARIO_DUAL_REORDER_AND_NEW_GROUP # noqa: F841
|
||
with self.harness.server_ctx():
|
||
recipe_id, ing_ids, _ = create_recipe_with_children(with_groups=False)
|
||
grp_id = str(uuid.uuid4())
|
||
db.session.add(
|
||
UnloadingGroup(
|
||
id=grp_id,
|
||
name="1 заезд",
|
||
distribution_type="percent",
|
||
value=75.0,
|
||
weight=1000.0,
|
||
order=1,
|
||
recipe_id=recipe_id,
|
||
created_by="system",
|
||
updated_by="system",
|
||
)
|
||
)
|
||
db.session.commit()
|
||
|
||
self._seed_recipe_on_terminals(recipe_id)
|
||
|
||
with self.harness.server_ctx():
|
||
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])
|
||
grp1 = db.session.get(UnloadingGroup, grp_id)
|
||
payload = recipe_update_payload(
|
||
recipe,
|
||
ingredients=[
|
||
ingredient_payload_from_row(ing_b, order=1),
|
||
ingredient_payload_from_row(ing_a, order=2),
|
||
],
|
||
groups=[
|
||
group_payload_from_row(grp1, order=1),
|
||
{
|
||
"name": "2 заезд",
|
||
"distribution_type": "percent",
|
||
"value": 25.0,
|
||
"weight": 400.0,
|
||
"order": 2,
|
||
},
|
||
],
|
||
)
|
||
update_recipe_from_payload(recipe_id, payload)
|
||
db.session.commit()
|
||
|
||
self.harness.drain_sync()
|
||
|
||
for term in ("a", "b"):
|
||
self.assertEqual(
|
||
self._ingredient_orders(term, recipe_id),
|
||
[(ing_ids[1], 1), (ing_ids[0], 2)],
|
||
msg=f"ingredient order terminal {term}",
|
||
)
|
||
snap = self._group_snapshot(term, recipe_id)
|
||
self.assertEqual(len(snap), 2, msg=f"groups count terminal {term}")
|
||
self.assertEqual(snap[0][2], 75.0)
|
||
self.assertEqual(snap[1], ("2 заезд", "percent", 25.0))
|
||
|
||
def test_split_groups_60_40_on_both_terminals_not_140_percent(self) -> None:
|
||
SCENARIO_DUAL_ADD_GROUP_UPDATES_EXISTING # noqa: F841
|
||
with self.harness.server_ctx():
|
||
recipe_id, ing_ids, _ = create_recipe_with_children(with_groups=False)
|
||
grp_id = str(uuid.uuid4())
|
||
db.session.add(
|
||
UnloadingGroup(
|
||
id=grp_id,
|
||
name="4 гр 1 зам",
|
||
distribution_type="percent",
|
||
value=100.0,
|
||
weight=1590.0,
|
||
order=1,
|
||
recipe_id=recipe_id,
|
||
created_by="system",
|
||
updated_by="system",
|
||
)
|
||
)
|
||
db.session.commit()
|
||
|
||
self._seed_recipe_on_terminals(recipe_id)
|
||
|
||
with self.harness.server_ctx():
|
||
recipe = db.session.get(Recipe, recipe_id)
|
||
grp1 = db.session.get(UnloadingGroup, grp_id)
|
||
ings = [
|
||
ingredient_payload_from_row(db.session.get(Ingredient, iid))
|
||
for iid in ing_ids
|
||
]
|
||
payload = recipe_update_payload(
|
||
recipe,
|
||
ingredients=ings,
|
||
groups=[
|
||
{
|
||
**group_payload_from_row(grp1, order=1),
|
||
"value": 60.0,
|
||
},
|
||
{
|
||
"name": "еще одна",
|
||
"distribution_type": "percent",
|
||
"value": 40.0,
|
||
"weight": 635.0,
|
||
"order": 2,
|
||
},
|
||
],
|
||
)
|
||
update_recipe_from_payload(recipe_id, payload)
|
||
db.session.commit()
|
||
|
||
self.harness.drain_sync()
|
||
|
||
for term in ("a", "b"):
|
||
snap = self._group_snapshot(term, recipe_id)
|
||
self.assertEqual(len(snap), 2, msg=f"terminal {term}")
|
||
self.assertEqual(snap[0][2], 60.0, msg=f"grp1 value terminal {term}")
|
||
self.assertEqual(snap[1][2], 40.0, msg=f"grp2 value terminal {term}")
|
||
total_percent = sum(v for _, t, v in snap if t == "percent")
|
||
self.assertAlmostEqual(total_percent, 100.0, places=1)
|
||
|
||
def test_dry_matter_locked_syncs_to_both_terminals(self) -> None:
|
||
with self.harness.server_ctx():
|
||
recipe_id, ing_ids, _ = create_recipe_with_children(with_groups=False)
|
||
recipe = db.session.get(Recipe, recipe_id)
|
||
recipe.dry_matter_locked = True
|
||
db.session.commit()
|
||
self._seed_recipe_on_terminals(recipe_id)
|
||
|
||
with self.harness.server_ctx():
|
||
recipe = db.session.get(Recipe, recipe_id)
|
||
comp_id = db.session.get(Ingredient, ing_ids[0]).component_id
|
||
update_recipe_from_payload(
|
||
recipe_id,
|
||
recipe_update_payload(
|
||
recipe,
|
||
ingredients=[
|
||
{
|
||
"id": ing_ids[0],
|
||
"component_id": comp_id,
|
||
"dry_matter": 50.0,
|
||
"dry_matter_per_head": 1.0,
|
||
"order": 1,
|
||
}
|
||
],
|
||
groups=[],
|
||
)
|
||
| {"dry_matter_locked": True, "heads_count": 10, "trip_percent": 100.0},
|
||
)
|
||
db.session.commit()
|
||
|
||
self.harness.drain_sync()
|
||
|
||
for term in ("a", "b"):
|
||
row = self.harness.recipe_on_terminal(term, recipe_id)
|
||
self.assertTrue(row.dry_matter_locked, msg=f"terminal {term}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|