"""Хелперы: мастер-рацион vs план на день vs экран оператора загрузки.""" from __future__ import annotations from datetime import date from typing import Any from flask.testing import FlaskClient DAILY_PLAN_SYNC_TABLES: tuple[str, ...] = ( "daily_trip_skip", "daily_ingredient_skip", "daily_unloading_group_skip", "daily_ingredient_replacement", "daily_component_norm_adjustment", ) KIOSK_HEADERS = {"X-Wesp-Kiosk": "1"} def plan_date_today() -> str: return date.today().isoformat() def master_recipe_json(client: FlaskClient, recipe_id: str) -> dict[str, Any]: resp = client.get(f"/api/recipes/{recipe_id}") assert resp.status_code == 200, resp.get_data(as_text=True) return resp.get_json() def plan_recipe_json( client: FlaskClient, recipe_id: str, *, plan_date: str | None = None, kiosk: bool = True, ) -> dict[str, Any]: iso = (plan_date or plan_date_today())[:10] headers = KIOSK_HEADERS if kiosk else {} resp = client.get( f"/api/recipes/{recipe_id}?date={iso}", headers=headers, ) assert resp.status_code == 200, resp.get_data(as_text=True) return resp.get_json() def first_ingredient_amount(payload: dict[str, Any]) -> float: ingredients = payload.get("ingredients") or [] assert ingredients, "expected at least one ingredient" return float(ingredients[0].get("amount") or 0) def ingredient_amounts(payload: dict[str, Any]) -> list[float]: return [float(i.get("amount") or 0) for i in (payload.get("ingredients") or [])] def operator_weight_display(client: FlaskClient, recipe_id: str) -> dict[str, Any]: set_resp = client.post("/api/set_current_recipe", json={"recipe_id": recipe_id}) assert set_resp.status_code == 200, set_resp.get_data(as_text=True) display = client.get("/api/weight_display_data") assert display.status_code == 200, display.get_data(as_text=True) return display.get_json() def assert_operator_matches_plan_not_master( client: FlaskClient, recipe_id: str, *, plan_date: str | None = None, expect_plan_differs_from_master: bool = True, ) -> dict[str, Any]: """Оператор (weight_display) = план; мастер — базовый рецепт без date=.""" master = master_recipe_json(client, recipe_id) plan = plan_recipe_json(client, recipe_id, plan_date=plan_date) operator = operator_weight_display(client, recipe_id) assert operator.get("status") == "active", operator plan_amounts = ingredient_amounts(plan) master_amounts = ingredient_amounts(master) assert plan_amounts, "plan must expose ingredients" if expect_plan_differs_from_master: assert plan_amounts != master_amounts or plan.get("ingredients") != master.get( "ingredients" ), "plan should differ from master for this scenario" assert operator["total_component"] == int(round(plan_amounts[0])) assert operator["total_mixture"] == int(round(sum(plan_amounts))) assert operator["total_component"] != int(round(master_amounts[0])) or len( plan_amounts ) != len(master_amounts) return { "master": master, "plan": plan, "operator": operator, "master_amounts": master_amounts, "plan_amounts": plan_amounts, } def assert_daily_plan_tables_registered() -> None: from app.models import ( DailyComponentNormAdjustment, DailyIngredientReplacement, DailyIngredientSkip, DailyTripSkip, DailyUnloadingGroupSkip, _sync_models, ) from app.services.sync_manager import SERVER_MASTER_TABLES, SNAPSHOT_MODELS from app.services.sync_record_data import get_record_data_for_sync model_by_table = { DailyTripSkip.__tablename__: DailyTripSkip, DailyIngredientSkip.__tablename__: DailyIngredientSkip, DailyUnloadingGroupSkip.__tablename__: DailyUnloadingGroupSkip, DailyIngredientReplacement.__tablename__: DailyIngredientReplacement, DailyComponentNormAdjustment.__tablename__: DailyComponentNormAdjustment, } assert set(DAILY_PLAN_SYNC_TABLES) == set(model_by_table) sync_model_tables = {m.__tablename__ for m in _sync_models if m.__tablename__.startswith("daily_")} assert sync_model_tables == set(DAILY_PLAN_SYNC_TABLES) snapshot_tables = {m.__tablename__ for m in SNAPSHOT_MODELS} for table in DAILY_PLAN_SYNC_TABLES: assert table in snapshot_tables, f"{table} missing from SNAPSHOT_MODELS" assert table in SERVER_MASTER_TABLES, f"{table} missing from SERVER_MASTER_TABLES" # smoke: get_record_data_for_sync resolves model (None без строки — ок) assert get_record_data_for_sync("daily_trip_skip", "nonexistent-id") is None