(parsed)
| 538 | |
| 539 | |
| 540 | def _validate_recipe(parsed): |
| 541 | required = {"name", "servings", "ingredients", "steps", "prep_time_minutes"} |
| 542 | missing = required - parsed.keys() |
| 543 | if missing: |
| 544 | return False, f"Missing fields: {missing}" |
| 545 | if not isinstance(parsed["name"], str) or not parsed["name"]: |
| 546 | return False, "name must be a non-empty string" |
| 547 | if not isinstance(parsed["servings"], int) or parsed["servings"] <= 0: |
| 548 | return False, f"servings must be positive int: {parsed['servings']!r}" |
| 549 | ings = parsed["ingredients"] |
| 550 | if not isinstance(ings, list) or len(ings) < 2: |
| 551 | return False, f"ingredients must be array of >=2: got {ings!r}" |
| 552 | for i, ing in enumerate(ings): |
| 553 | if not isinstance(ing, dict): |
| 554 | return False, f"ingredient[{i}] is not an object: {ing!r}" |
| 555 | ing_d = cast(dict[str, Any], ing) |
| 556 | item_val = ing_d.get("item") |
| 557 | qty_val = ing_d.get("quantity") |
| 558 | if item_val is None or qty_val is None: |
| 559 | return False, f"ingredient[{i}] missing item/quantity: {ing!r}" |
| 560 | if not isinstance(item_val, str) or not isinstance(qty_val, str): |
| 561 | return False, f"ingredient[{i}] fields must be strings: {ing!r}" |
| 562 | steps = parsed["steps"] |
| 563 | if not isinstance(steps, list) or len(steps) < 2: |
| 564 | return False, f"steps must be array of >=2 strings: got {steps!r}" |
| 565 | if not all(isinstance(s, str) and s for s in steps): |
| 566 | return False, "all steps must be non-empty strings" |
| 567 | pt = parsed["prep_time_minutes"] |
| 568 | if not isinstance(pt, int) or pt <= 0: |
| 569 | return False, f"prep_time_minutes must be positive int: {pt!r}" |
| 570 | return True, f"recipe '{parsed['name']}' with {len(ings)} ingredients, {len(steps)} steps" |
| 571 | |
| 572 | |
| 573 | # ---- Test 4: Tool call -> structured product comparison (after_tools) ---- |
no test coverage detected