"""Coverage for the daily-prep hardening pass. Targets the two prep-quality defects observed via the fable MCP on 2026-05-19 (conversation #291): 1. Fabricated weather ("partly cloudy with a high of 68°F and a 15% chance of rain") emitted with an empty weather section. 2. Self-contradictory overdue phrasing ("still on the list, not currently due, 88 days ago"; "1 days ago"). All targets are pure sync functions — no DB / LLM needed. """ import datetime from fabledassistant.services.journal_prep import ( _prose_fabricated_weather, _render_sections_for_prompt, _render_task_line, ) TODAY = datetime.date(2026, 5, 19) # ── Overdue phrasing ──────────────────────────────────────────────────────── def test_overdue_line_singular_day(): line = _render_task_line( {"title": "Change my oil", "due_date": "2026-05-18", "days_overdue": 1}, include_due=False, include_overdue=True, ) assert "1 day overdue" in line assert "1 days" not in line # the old ungrammatical form is gone def test_overdue_line_plural_days_no_contradiction(): line = _render_task_line( {"title": "Research X", "due_date": "2026-02-20", "days_overdue": 88}, include_due=False, include_overdue=True, ) assert "88 days overdue" in line assert "was due 2026-02-20" in line assert "days ago" not in line # replaced by unambiguous "overdue" def test_overdue_section_header_is_unambiguous(): out = _render_sections_for_prompt({ "tasks_overdue": [ {"title": "Research X", "due_date": "2026-02-20", "days_overdue": 88}, ], }) assert "past their due date, still open" in out # The phrase that the model parroted into the self-contradiction is gone. assert "not currently due" not in out # ── Weather absent-marker ─────────────────────────────────────────────────── def test_empty_weather_emits_explicit_none_marker(): out = _render_sections_for_prompt({ "tasks_overdue": [ {"title": "Change my oil", "due_date": "2026-05-18", "days_overdue": 1}, ], "weather": [], }) assert "WEATHER: none available" in out assert "Do NOT mention weather" in out def test_present_weather_renders_data_not_marker(): out = _render_sections_for_prompt({ "tasks_due_today": [{"title": "Ship it"}], "weather": [{ "location_label": "Home", "forecast_json": {"daily": { "temperature_2m_max": [70], "temperature_2m_min": [52], "precipitation_probability_max": [10], }}, }], }) assert "WEATHER:" in out assert "none available" not in out assert "high 70°" in out def test_quiet_day_returns_fabrication_forbidding_text(): out = _render_sections_for_prompt({}) assert "quiet day" in out assert "Do not invent weather" in out # ── Fabricated-weather guard ──────────────────────────────────────────────── def test_guard_flags_the_real_observed_hallucination(): prose = ( "You have two overdue tasks. The weather today is partly cloudy " "with a high of 68°F and a 15% chance of rain. What's on your mind?" ) assert _prose_fabricated_weather(prose, {"weather": []}) is True def test_guard_flags_bare_temperature_glyph(): assert _prose_fabricated_weather("Expect around 72° later.", {"weather": []}) is True def test_guard_passes_clean_prose(): prose = "Two tasks are overdue and nothing is due today. What's on your mind?" assert _prose_fabricated_weather(prose, {"weather": []}) is False def test_guard_no_false_positive_on_rain_in_task_title(): # Bare "rain" must NOT trip the guard — only "chance of rain" etc. does. prose = "Don't forget to buy rain boots and finish the training module." assert _prose_fabricated_weather(prose, {"weather": []}) is False def test_guard_allows_weather_when_data_present(): prose = "Home is 70°F with a 10% chance of rain." sections = {"weather": [{"location_label": "Home"}]} assert _prose_fabricated_weather(prose, sections) is False