feat: Initial commit of FitTrack_GarminSync project

This commit is contained in:
2025-10-10 12:20:48 -07:00
parent d0e29fbeb4
commit 18f9f6fa18
229 changed files with 21035 additions and 42 deletions

View File

@@ -0,0 +1,137 @@
import pytest
from unittest.mock import AsyncMock, patch
from src.services.auth_service import AuthService
from src.schemas import UserCreate, User, TokenCreate, TokenUpdate
import uuid
@pytest.fixture
def auth_service():
"""Fixture for AuthService with mocked CentralDBService."""
with patch('src.services.auth_service.CentralDBService') as MockCentralDBService:
mock_central_db_instance = MockCentralDBService.return_value
mock_central_db_instance.get_user_by_email = AsyncMock()
mock_central_db_instance.create_user = AsyncMock()
mock_central_db_instance.get_token = AsyncMock()
mock_central_db_instance.create_token = AsyncMock()
mock_central_db_instance.update_token = AsyncMock()
service = AuthService()
service.central_db = mock_central_db_instance
yield service
@pytest.fixture
def mock_garth_login():
"""Fixture to mock garth.login."""
with patch('garth.login') as mock_login:
yield mock_login
@pytest.fixture
def mock_garth_client():
"""Fixture to mock garth.client attributes."""
with patch('garth.client') as mock_client:
mock_client.oauth2_token = "mock_oauth2_token"
mock_client.refresh_token = "mock_refresh_token"
mock_client.token_expires_at = 1234567890
yield mock_client
@pytest.mark.asyncio
async def test_authenticate_garmin_connect_new_user_success(auth_service, mock_garth_login, mock_garth_client):
"""Test successful Garmin authentication with a new user."""
email = "new_user@example.com"
password = "password123"
mock_user = User(id=uuid.uuid4(), name=email, email=email)
auth_service.central_db.get_user_by_email.return_value = None
auth_service.central_db.create_user.return_value = mock_user
auth_service.central_db.get_token.return_value = None
result = await auth_service.authenticate_garmin_connect(email, password)
mock_garth_login.assert_called_once_with(email, password)
auth_service.central_db.get_user_by_email.assert_called_once_with(email=email)
auth_service.central_db.create_user.assert_called_once()
auth_service.central_db.create_token.assert_called_once()
auth_service.central_db.update_token.assert_not_called()
assert result == {"message": "Garmin Connect authentication successful", "user_id": str(mock_user.id)}
@pytest.mark.asyncio
async def test_authenticate_garmin_connect_existing_user_success(auth_service, mock_garth_login, mock_garth_client):
"""Test successful Garmin authentication with an existing user and no existing token."""
email = "existing_user@example.com"
password = "password123"
mock_user = User(id=uuid.uuid4(), name=email, email=email)
auth_service.central_db.get_user_by_email.return_value = mock_user
auth_service.central_db.get_token.return_value = None
result = await auth_service.authenticate_garmin_connect(email, password)
mock_garth_login.assert_called_once_with(email, password)
auth_service.central_db.get_user_by_email.assert_called_once_with(email=email)
auth_service.central_db.create_user.assert_not_called()
auth_service.central_db.create_token.assert_called_once()
auth_service.central_db.update_token.assert_not_called()
assert result == {"message": "Garmin Connect authentication successful", "user_id": str(mock_user.id)}
@pytest.mark.asyncio
async def test_authenticate_garmin_connect_existing_user_existing_token_success(auth_service, mock_garth_login, mock_garth_client):
"""Test successful Garmin authentication with an existing user and existing token."""
email = "existing_user_token@example.com"
password = "password123"
mock_user = User(id=uuid.uuid4(), name=email, email=email)
mock_user_id = mock_user.id # Capture the generated UUID
mock_existing_token = TokenCreate(
access_token="old_access", refresh_token="old_refresh", expires_at=1111111111, user_id=mock_user_id
)
auth_service.central_db.get_user_by_email.return_value = mock_user
auth_service.central_db.get_token.return_value = mock_existing_token
result = await auth_service.authenticate_garmin_connect(email, password)
mock_garth_login.assert_called_once_with(email, password)
auth_service.central_db.get_user_by_email.assert_called_once_with(email=email)
auth_service.central_db.create_user.assert_not_called()
auth_service.central_db.get_token.assert_called_once_with(user_id=mock_user_id)
auth_service.central_db.update_token.assert_called_once()
auth_service.central_db.create_token.assert_not_called()
assert result == {"message": "Garmin Connect authentication successful", "user_id": str(mock_user.id)}
@pytest.mark.asyncio
async def test_authenticate_garmin_connect_garmin_failure(auth_service, mock_garth_login):
"""Test Garmin authentication failure."""
email = "fail_garmin@example.com"
password = "password123"
mock_garth_login.side_effect = Exception("Garmin login failed")
result = await auth_service.authenticate_garmin_connect(email, password)
mock_garth_login.assert_called_once_with(email, password)
auth_service.central_db.get_user_by_email.assert_not_called()
auth_service.central_db.create_user.assert_not_called()
auth_service.central_db.get_token.assert_not_called()
auth_service.central_db.create_token.assert_not_called()
auth_service.central_db.update_token.assert_not_called()
assert result is None
@pytest.mark.asyncio
async def test_authenticate_garmin_connect_central_db_user_creation_failure(auth_service, mock_garth_login, mock_garth_client):
"""Test CentralDB user creation failure."""
email = "fail_user_create@example.com"
password = "password123"
auth_service.central_db.get_user_by_email.return_value = None
auth_service.central_db.create_user.return_value = None
result = await auth_service.authenticate_garmin_connect(email, password)
mock_garth_login.assert_called_once_with(email, password)
auth_service.central_db.get_user_by_email.assert_called_once_with(email=email)
auth_service.central_db.create_user.assert_called_once()
auth_service.central_db.get_token.assert_not_called()
auth_service.central_db.create_token.assert_not_called()
auth_service.central_db.update_token.assert_not_called()
assert result is None

View File

@@ -0,0 +1,37 @@
import pytest
from unittest.mock import MagicMock, patch
from fastapi import HTTPException
from src.services.rate_limiter import RateLimiter
import asyncio
@pytest.mark.asyncio
async def test_rate_limiter_allows_requests_within_limit():
"""Test that the rate limiter allows requests that are within the limit."""
rate_limiter = RateLimiter(rate_limit="2/second")
mock_request = MagicMock()
try:
await rate_limiter(mock_request)
await rate_limiter(mock_request)
except HTTPException:
pytest.fail("HTTPException raised unexpectedly.")
@pytest.mark.asyncio
async def test_rate_limiter_raises_exception_when_exceeded():
"""Test that the rate limiter raises an HTTPException when the rate limit is exceeded."""
rate_limiter = RateLimiter(rate_limit="1/second")
mock_request = MagicMock()
# Mock the limiter.test method
with patch.object(rate_limiter.limiter, 'test') as mock_limiter_test:
mock_limiter_test.side_effect = [True, False] # First call returns True, second returns False
await rate_limiter(mock_request) # First call, should pass
with pytest.raises(HTTPException) as exc_info:
await rate_limiter(mock_request) # Second call, should fail
assert exc_info.value.status_code == 429
mock_limiter_test.assert_called_with(rate_limiter.rate_limit_item, "single_user_system")
assert exc_info.value.status_code == 429

View File

@@ -0,0 +1,34 @@
import uuid
from datetime import datetime
from unittest.mock import MagicMock
import pytest
from src.services.sync_status_service import SyncStatusService
from src.jobs import SyncJob, JobStore
@pytest.fixture
def mock_job_store():
"""Fixture to create a mock JobStore."""
job_store = MagicMock(spec=JobStore)
job_id = uuid.uuid4()
job = SyncJob(id=str(job_id), status="completed", created_at=datetime.utcnow())
job_store.get_all_jobs.return_value = [job]
job_store.get_job.return_value = job
return job_store
def test_get_sync_jobs_all(mock_job_store):
"""Test retrieving all sync jobs."""
service = SyncStatusService(job_store=mock_job_store)
jobs = service.get_sync_jobs()
assert len(jobs) == 1
mock_job_store.get_all_jobs.assert_called_once()
def test_get_sync_job_by_id(mock_job_store):
"""Test retrieving a single sync job by ID."""
service = SyncStatusService(job_store=mock_job_store)
job_id = mock_job_store.get_job.return_value.id
# The get_sync_jobs implementation filters all jobs, so we need to mock get_all_jobs
mock_job_store.get_all_jobs.return_value = [mock_job_store.get_job.return_value]
jobs = service.get_sync_jobs(job_id=job_id)
assert len(jobs) == 1
assert jobs[0].id == str(job_id)