Files
FitTrack2/FitnessSync/backend/tests/integration/test_activities_api.py
sstent d1cfd0fd8e feat: implement Fitbit OAuth, Garmin MFA, and optimize segment discovery
- Add Fitbit authentication flow (save credentials, OAuth callback handling)
- Implement Garmin MFA support with successful session/cookie handling
- Optimize segment discovery with new sampling and activity query services
- Refactor database session management in discovery API for better testability
- Enhance activity data parsing for charts and analysis
- Update tests to use testcontainers and proper dependency injection
- Clean up repository by ignoring and removing tracked transient files (.pyc, .db)
2026-01-16 15:35:26 -08:00

154 lines
4.8 KiB
Python

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] == []