from __future__ import annotations import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parents[1])) from schema_contract import migrate_training_config, validate_training_config def sample_config(): return { "meta": {"title": "Test", "weeks": 2}, "training_format": {"mode": "tabata", "fixed_interval": True, "work_seconds": 20, "rest_seconds": 10, "rounds": 8}, "phases": {"items": [{"name": "Start", "weeks": 1}, {"name": "Aufbau", "weeks": 1}]}, "days": [{"num": 1, "focus": "A", "rotations": [{"label": "Block", "exercises": [{ "name": "Squat-Progression", "key": "squat", "result_schema": {"mode": "reps", "weight_mode": "none", "laterality": "bilateral"} }]}]}], "stages": {"squat": {"name": "Squat-Progression", "steps": [ "Deep Squat Hold", "Volle Squats" ], "result_schemas": [ {"mode": "seconds", "weight_mode": "optional", "laterality": "bilateral"}, {"mode": "reps", "weight_mode": "optional", "laterality": "bilateral"}, ]}}, } def test_migration_creates_stable_ids_object_steps_and_catalog(): first = migrate_training_config(sample_config(), plan_id="test") second = migrate_training_config(first, plan_id="test") assert first["schema_version"] == 3 assert first["contract_version"] == 2 assert first["days"][0]["id"] == second["days"][0]["id"] exercise = first["days"][0]["rotations"][0]["exercises"][0] assert exercise["id"] == second["days"][0]["rotations"][0]["exercises"][0]["id"] steps = first["stages"]["squat"]["steps"] assert all(isinstance(step, dict) and step.get("id") for step in steps) assert steps[0]["result_schema"]["mode"] == "seconds" assert steps[1]["result_schema"]["mode"] == "reps" assert first["exercise_catalog"] def test_validation_accepts_complete_plan(): result = validate_training_config(sample_config()) assert result["errors"] == [] def test_missing_result_schemas_are_migrated_to_explicit_defaults(): cfg = sample_config() exercise = cfg["days"][0]["rotations"][0]["exercises"][0] exercise.pop("result_schema", None) cfg["stages"]["squat"].pop("result_schemas", None) migrated = migrate_training_config(cfg, plan_id="test") ex_schema = migrated["days"][0]["rotations"][0]["exercises"][0]["result_schema"] assert ex_schema["mode"] == "reps" steps = migrated["stages"]["squat"]["steps"] assert steps[0]["result_schema"]["mode"] == "seconds" assert steps[1]["result_schema"]["mode"] == "reps" assert validate_training_config(migrated)["errors"] == [] def test_legacy_position_id_is_persisted_across_reordering(): migrated = migrate_training_config(sample_config(), plan_id="test") exercise = migrated["days"][0]["rotations"][0]["exercises"][0] legacy_id = exercise["legacy_id"] exercise_id = exercise["id"] migrated["days"][0]["rotations"][0]["exercises"].insert(0, { "name": "Neue Übung", "key": "new", "result_schema": {"mode": "reps"} }) remigrated = migrate_training_config(migrated, plan_id="test") original = next(item for item in remigrated["days"][0]["rotations"][0]["exercises"] if item["id"] == exercise_id) assert original["legacy_id"] == legacy_id