nicholasg1997
feat: add Local AI Running Coach with Gradio UI for personalized coaching reports
90b4eb9 | """Tests for the text rendering layer (render.py). | |
| The critical one is `test_no_none_leaks_*`: if a missing value ever renders as | |
| the literal "None", it silently poisons the model's prompt. | |
| """ | |
| from datetime import date, timedelta | |
| from rate_my_run.models import WeekStats, TrainingSummary | |
| from rate_my_run.render import format_pace, summary_to_text | |
| def _empty_weeks(n=8): | |
| """n empty weekly buckets (oldest -> newest).""" | |
| return [WeekStats(date.today() - timedelta(days=7 * (n - i)), 0, 0.0, 0.0, None) | |
| for i in range(n)] | |
| def _summary(weeks, days_since=1, mileage=None, trend="insufficient_data", signals=None): | |
| return TrainingSummary( | |
| weeks=weeks, | |
| days_since_last_run=days_since, | |
| mileage_change_pct=mileage, | |
| pace_trend=trend, | |
| signals=signals or [], | |
| ) | |
| # --- format_pace ------------------------------------------------------------ | |
| def test_format_pace_basic(): | |
| assert format_pace(5.0) == "5:00/km" | |
| assert format_pace(5.5) == "5:30/km" | |
| def test_format_pace_none(): | |
| assert format_pace(None) == "n/a" | |
| def test_format_pace_rounds_60s_up(): | |
| # 5.999 min would round to 5:60 -> must become 6:00 | |
| assert format_pace(5.999) == "6:00/km" | |
| # --- summary_to_text -------------------------------------------------------- | |
| def test_no_none_leaks_with_missing_data(): | |
| # All-empty history: pace None, mileage None, insufficient pace trend. | |
| text = summary_to_text(_summary(_empty_weeks())) | |
| assert "None" not in text | |
| def test_goal_is_included_when_given(): | |
| text = summary_to_text(_summary(_empty_weeks()), goal="run a sub-25 5K") | |
| assert "sub-25 5K" in text | |
| def test_goal_omitted_when_absent(): | |
| text = summary_to_text(_summary(_empty_weeks())) | |
| assert "stated goal" not in text | |
| def test_signals_render_as_descriptions_not_raw_keys(): | |
| text = summary_to_text(_summary(_empty_weeks(), signals=["mileage_spike"])) | |
| assert "Weekly mileage rose more than 10%" in text | |
| assert "mileage_spike" not in text # raw enum key must not leak through | |