from datetime import UTC, datetime, timedelta from types import SimpleNamespace import pytest from app.records import best_efforts, persist_efforts, record_book def run(points, id=1, sport="running"): activity = SimpleNamespace( id=id, sport=sport, started_at=datetime(2026, 1, 1, tzinfo=UTC) + timedelta(days=id), samples=[{"distance": d, "time": t} for d, t in points], ) persist_efforts(activity) return activity def test_fastest_window_can_start_between_samples(): efforts = best_efforts(run([(0, 0), (600, 300), (1500, 480), (2000, 730)])) assert efforts[0]["seconds"] == pytest.approx(230) assert efforts[0]["start_m"] == 500 assert efforts[1]["seconds"] == 730 def test_stops_inside_count_but_boundary_stops_do_not(): efforts = best_efforts(run([(0, 0), (0, 60), (500, 210), (500, 270), (1000, 420), (1000, 480)])) assert efforts[0]["seconds"] == 360 @pytest.mark.parametrize( "middle", [ [(None, 150)], [(float("nan"), 150)], [(400, 200), (100, 210)], [(500, 150), (600, 140)], ], ) def test_invalid_or_reset_data_cannot_be_bridged(middle): assert best_efforts(run([(0, 0), *middle, (1000, 330)])) == [] def test_missing_time_short_run_and_other_sports_have_no_records(): assert best_efforts(run([(0, None), (5000, None)])) == [] assert best_efforts(run([(0, 0), (999.99, 300)])) == [] assert best_efforts(run([(0, 0), (5000, 300)], sport="cycling")) == [] def test_record_history_backfills_ties_and_improvements(): first = run([(0, 0), (1000, 300)], id=1) faster = run([(0, 0), (1000, 290)], id=2) tie = run([(0, 0), (1000, 290)], id=3) slower = run([(0, 0), (1000, 320)], id=4) book = record_book([slower, tie, faster, first]) assert book["records"][0]["activity"] is faster assert [book["runs"][i][0]["pr"] for i in range(1, 5)] == [True, True, False, False] assert record_book([first, slower])["records"][0]["activity"] is first def test_all_distances_and_timestamp_gaps_use_elapsed_time(): efforts = best_efforts(run([(0, 100), (42195, 12758.5)])) assert len(efforts) == 6 assert efforts[0]["seconds"] == pytest.approx(300) assert efforts[-1]["seconds"] == 12658.5