@@ -0,0 +1,239 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user