Files
FitTrack2/FitnessSync/backend/tests/unit/test_fitbit_auth.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

112 lines
3.8 KiB
Python

import pytest
from unittest.mock import MagicMock, patch
from fastapi.testclient import TestClient
from main import app
from src.api.auth import get_db, FitbitCredentials
from src.models import Configuration, APIToken
@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
yield TestClient(app)
app.dependency_overrides.clear()
def test_save_fitbit_credentials(client, mock_db_session):
"""Test saving Fitbit credentials and generating auth URL."""
payload = {
"client_id": "test_client_id",
"client_secret": "test_client_secret",
"redirect_uri": "http://localhost/callback"
}
# Mock DB query for existing config
mock_db_session.query.return_value.first.return_value = None
# Mock Config creation is handled by code logic (checks if exists, else creates)
with patch("src.api.auth.FitbitClient") as MockFitbitClient:
instance = MockFitbitClient.return_value
instance.get_authorization_url.return_value = "https://www.fitbit.com/oauth2/authorize?client_id=test_client_id"
response = client.post("/api/setup/fitbit", json=payload)
assert response.status_code == 200
data = response.json()
assert "auth_url" in data
assert "test_client_id" in data["auth_url"]
# Verify DB interactions
# Should add new config
assert mock_db_session.add.called
assert mock_db_session.commit.called
def test_fitbit_callback_success(client, mock_db_session):
"""Test Fitbit OAuth callback success."""
# Setup initial config in mock DB
mock_config = MagicMock(spec=Configuration)
mock_config.fitbit_client_id = "cid"
mock_config.fitbit_client_secret = "csec"
mock_config.fitbit_redirect_uri = "uri"
mock_db_session.query.return_value.first.return_value = mock_config
# Mock Token query (return None so it creates new)
# query(Configuration).first() -> config
# query(APIToken).filter_by().first() -> None (to trigger creation)
def query_side_effect(model):
m = MagicMock()
if model == Configuration:
m.first.return_value = mock_config
elif model == APIToken:
m.filter_by.return_value.first.return_value = None
return m
mock_db_session.query.side_effect = query_side_effect
with patch("src.api.auth.FitbitClient") as MockFitbitClient:
instance = MockFitbitClient.return_value
instance.exchange_code_for_token.return_value = {
"access_token": "new_at",
"refresh_token": "new_rt",
"expires_in": 3600,
"user_id": "uid",
"scope": ["weight"]
}
payload = {"code": "auth_code_123"}
response = client.post("/api/setup/fitbit/callback", json=payload)
assert response.status_code == 200
assert response.json()["status"] == "success"
# Verify Token saved
assert mock_db_session.add.called # APIToken added
assert mock_db_session.commit.called
def test_fitbit_callback_no_config(client, mock_db_session):
"""Test callback fails if no config exists."""
# Mock DB returns None for config
def query_side_effect(model):
m = MagicMock()
if model == Configuration:
m.first.return_value = None # No config
return m
mock_db_session.query.side_effect = query_side_effect
payload = {"code": "auth_code_123"}
response = client.post("/api/setup/fitbit/callback", json=payload)
assert response.status_code == 400
assert "Configuration missing" in response.json()["detail"]