from unittest.mock import MagicMock import sys # Mock fitdecode before imports since it might not be installed in local env (running in docker) sys.modules['fitdecode'] = MagicMock() import pytest from src.utils.geo import ramer_douglas_peucker, haversine_distance, calculate_bounds from src.services.segment_matcher import SegmentMatcher from src.models.activity import Activity from src.models.segment import Segment from datetime import datetime, timedelta import json from unittest.mock import patch def test_haversine(): # Dist between (0,0) and (0,1) deg is ~111km d = haversine_distance(0, 0, 0, 1) # 1 deg lat ~ 111.32 km assert 110000 < d < 112000 def test_rdp_simple(): # Points on a line points = [[0,0], [1,1], [2,2]] # Should simplify to [0,0], [2,2] simplified = ramer_douglas_peucker(points, epsilon=0.1) assert len(simplified) == 2 assert simplified[0] == [0,0] assert simplified[1] == [2,2] def test_rdp_peak(): # Triangle points = [[0,0], [1,10], [2,0]] # [lon, lat] note: RDP expects [lon, lat] usually? # My RDP implementation uses x,y so order doesn't matter for geometric shape simplified = ramer_douglas_peucker(points, epsilon=1.0) assert len(simplified) == 3 def test_bounds(): points = [[0,0], [10, 10], [-5, 5]] bounds = calculate_bounds(points) assert bounds['min_lat'] == 0 # wait, index 1 is lat? check utils # If points are [lon, lat] # 0,0: lat=0 # 10,10: lat=10 # -5,5: lat=5 # bounds are min_lat=0, max_lat=10. min_lon=-5, max_lon=10 # My calculate_bounds implementation assumes [lon, lat] assert bounds['min_lat'] == 0 assert bounds['max_lat'] == 10 assert bounds['min_lon'] == -5 assert bounds['max_lon'] == 10 def test_matcher_logic(): # Mock DB session mock_session = MagicMock() # Create segment [0,0] -> [0, 0.01] (approx 1.1km north) segment_points = [[0,0], [0, 0.01]] segment = Segment( id=1, name="Test Seg", points=json.dumps(segment_points), bounds=json.dumps(calculate_bounds(segment_points)), distance=1110.0, activity_type='cycling' ) mock_session.query.return_value.filter.return_value.all.return_value = [segment] matcher = SegmentMatcher(mock_session) # Create activity trace that covers this # 0,0 at T=0 # 0,0.01 at T=100s act_points = [[0,0], [0, 0.005], [0, 0.01]] # Mock activity activity = Activity(id=100, activity_start_time=datetime.now()) # Matcher needs to use parsers internally? Or uses slice of points? # Matcher logic (_match_segment) uses points list passed to match_activity # Wait, _match_segment needs timestamps to calc elapsed time. # We need to mock extract_timestamps_from_file or patch it from unittest.mock import patch with patch('src.services.segment_matcher.extract_timestamps_from_file') as mock_extract: # 0,0@T0, 0,0.005@T50, 0,0.01@T100 start_time = datetime.now() timestamps = [start_time, start_time + timedelta(seconds=50), start_time + timedelta(seconds=100)] mock_extract.return_value = timestamps # Add dummy content activity.file_content = b'dummy' activity.file_type = 'fit' # Run match efforts = matcher.match_activity(activity, act_points) assert len(efforts) == 1 effort = efforts[0] assert effort.segment_id == 1 assert effort.elapsed_time == 100.0 if __name__ == "__main__": # verification try: test_haversine() test_rdp_simple() test_bounds() print("Geo Utils Passed") except Exception as e: print(f"Failed: {e}")