import pytest from unittest.mock import MagicMock from sqlalchemy.orm import Session from src.services.bike_matching import match_activity_to_bike, calculate_observed_ratio from src.models import Activity, BikeSetup @pytest.fixture def mock_db(): db = MagicMock(spec=Session) # Mock bike setups # 1. 50/15 => 3.33 # 2. 48/16 => 3.00 # 3. 52/11 => 4.72 setup1 = BikeSetup(id=1, frame="Fixie", chainring=50, rear_cog=15, name="Fixie A") setup2 = BikeSetup(id=2, frame="Commuter", chainring=48, rear_cog=16, name="City") setup3 = BikeSetup(id=3, frame="Road", chainring=52, rear_cog=11, name="Race") db.query.return_value.all.return_value = [setup1, setup2, setup3] return db def test_calculate_observed_ratio(): # Ratio = (Speed * 60) / (Cadence * 2.1) # Speed 10m/s (36kmh), Cadence 90 # Ratio = 600 / (90 * 2.1) = 600 / 189 = 3.17 ratio = calculate_observed_ratio(10, 90) assert abs(ratio - 3.17) < 0.01 def test_match_activity_success(mock_db): # Setup 2 is 48/16 = 3.0 # Target: Speed for Ratio 3.0 at 90 RPM # Speed = (3.0 * 90 * 2.1) / 60 = 567 / 60 = 9.45 m/s activity = Activity( id=101, activity_type='cycling', avg_speed=9.45, avg_cadence=90, streams=None # valid attribute ) match, confidence = match_activity_to_bike(mock_db, activity) assert match is not None assert match.id == 2 # Commuter (Ratio 3.0) def test_match_activity_indoor_ignored(mock_db): activity = Activity( id=102, activity_type='Indoor Cycling', avg_speed=9.45, avg_cadence=90, streams=None ) match, confidence = match_activity_to_bike(mock_db, activity) assert match is None def test_match_activity_no_match(mock_db): # Ratio 1.0 (Very low gear) # Speed = 3.15 m/s at 90 RPM activity = Activity( id=103, activity_type='cycling', avg_speed=3.15, avg_cadence=90, streams=None ) match, confidence = match_activity_to_bike(mock_db, activity) assert match is None