import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient from datetime import datetime, timedelta from src.api.activities import router from src.models.activity import Activity from src.models.bike_setup import BikeSetup from src.models.activity_state import GarminActivityState from src.api.activities import get_db from main import app # Use main app instance # Mock Objects matches Pydantic models structure where needed, # but for SQLAlchemy response matching, standard objects work. def mock_activity(garmin_id="12345", name="Test Ride"): # Mocking a SQLAlchemy Activity object act = MagicMock(spec=Activity) act.id = int(garmin_id) act.garmin_activity_id = garmin_id act.activity_name = name act.activity_type = "cycling" act.start_time = datetime(2023, 1, 1, 10, 0, 0) act.duration = 3600.0 act.file_type = "fit" act.download_status = "downloaded" act.downloaded_at = datetime(2023, 1, 1, 12, 0, 0) act.avg_power = 200 act.avg_hr = 150 act.avg_cadence = 90 act.is_estimated_power = False # Relationships bike = MagicMock(spec=BikeSetup) bike.id = 1 bike.name = "Road Bike" bike.frame = "Carbon" bike.chainring = 50 bike.rear_cog = 11 act.bike_setup = bike act.bike_setup_id = 1 # File content for details overrides act.file_content = b"mock_content" # Extended stats for details act.distance = 20000.0 act.calories = 800.0 act.max_hr = 180 act.avg_speed = 8.5 act.max_speed = 12.0 act.elevation_gain = 500.0 act.elevation_loss = 500.0 act.max_cadence = 100 act.steps = 0 act.vo2_max = 50.0 return act def mock_activity_state(garmin_id="12345", name="Test Ride"): state = MagicMock(spec=GarminActivityState) state.garmin_activity_id = garmin_id state.activity_name = name state.activity_type = "cycling" state.start_time = datetime(2023, 1, 1, 10, 0, 0) state.sync_status = "synced" return state @pytest.fixture def mock_db_session(): return MagicMock() @pytest.fixture def client(mock_db_session): def override_get_db(): try: yield mock_db_session finally: pass app.dependency_overrides[get_db] = override_get_db with TestClient(app) as c: yield c app.dependency_overrides.clear() def test_list_activities(client, mock_db_session): # Setup Mock Return # list_activities queries (GarminActivityState, Activity) state1 = mock_activity_state("1001", "Morning Ride") act1 = mock_activity("1001", "Morning Ride") state2 = mock_activity_state("1002", "Evening Ride") act2 = mock_activity("1002", "Evening Ride") # Mock query().outerjoin().order_by().offset().limit().all() chain # It's a bit long chain to mock perfectly. # db.query(...) returns Query object. mock_query = mock_db_session.query.return_value mock_query.outerjoin.return_value = mock_query mock_query.order_by.return_value = mock_query mock_query.offset.return_value = mock_query mock_query.limit.return_value = mock_query mock_query.all.return_value = [(state1, act1), (state2, act2)] response = client.get("/api/activities/list?limit=10&offset=0") # Check execution assert response.status_code == 200 data = response.json() assert len(data) == 2 assert data[0]["garmin_activity_id"] == "1001" assert data[0]["activity_name"] == "Morning Ride" assert data[0]["download_status"] == "downloaded" assert data[0]["bike_setup"]["name"] == "Road Bike" def test_get_activity_details(client, mock_db_session): # Setup act = mock_activity("2001", "Detail Test") # db.query(Activity).filter(...).first() mock_query = mock_db_session.query.return_value mock_query.filter.return_value = mock_query mock_query.first.return_value = act response = client.get(f"/api/activities/2001/details") assert response.status_code == 200 data = response.json() assert data["garmin_activity_id"] == "2001" assert data["distance"] == 20000.0 assert data["bike_setup"]["name"] == "Road Bike" def test_get_activity_streams_mock(client, mock_db_session): act = mock_activity("3001", "Stream Test") act.streams_json = None act.file_content = None # Mock query returning activity directly mock_db_session.query.return_value.filter.return_value.first.return_value = act response = client.get(f"/api/activities/3001/streams") assert response.status_code == 200 data = response.json() # Expect empty streams structure expected_keys = ["time", "heart_rate", "power", "altitude", "speed", "cadence", "respiration_rate"] for k in expected_keys: assert k in data assert data[k] == []