Complete spec: Code alignment and documentation cleanup

- Ensure code aligns with CentralDB models
- Document code alignment with CentralDB models
- Remove informal reference documents (data-model.md, DB_API_SPEC.json, GARMINSYNC_SPEC.md)
- Run linters and formatters (black, isort, mypy)
- Update project configuration files
- Add .dockerignore for Docker builds
- Perform code formatting and import sorting
- Fix type checking issues
- Update documentation files
- Complete implementation tasks as per spec
This commit is contained in:
2025-12-18 13:21:54 -08:00
parent b0aa585372
commit ca9d7d9e90
58 changed files with 2726 additions and 377 deletions

View File

@@ -3,14 +3,17 @@ from unittest.mock import AsyncMock, patch
import pytest
from src.schemas import TokenCreate, User
from src.services.auth_service import AuthService
from backend.src.models.central_db_models import User
from backend.src.schemas import TokenCreate
from backend.src.services.auth_service import AuthService
@pytest.fixture
def auth_service():
"""Fixture for AuthService with mocked CentralDBService."""
with patch("src.services.auth_service.CentralDBService") as MockCentralDBService:
with patch(
"backend.src.services.central_db_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()

View File

@@ -0,0 +1,73 @@
from datetime import datetime
from backend.src.models.central_db_models import (
Activity,
GarminConnectAccount,
GarminCredentials,
HealthMetric,
User,
Workout,
)
def test_user_model():
user = User(id="123", username="testuser", email="test@example.com")
assert user.id == "123"
assert user.username == "testuser"
assert user.email == "test@example.com"
def test_garmin_connect_account_model():
account = GarminConnectAccount(
id="abc",
user_id="123",
oauth_token="token",
oauth_token_secret="secret",
refresh_token="refresh",
token_expires_at=datetime.now(),
)
assert account.id == "abc"
assert account.user_id == "123"
def test_garmin_credentials_model():
credentials = GarminCredentials(
garmin_username="garmin@example.com",
garmin_password_plaintext="password",
access_token="access",
access_token_secret="access_secret",
token_expiration_date=datetime.now(),
)
assert credentials.garmin_username == "garmin@example.com"
def test_activity_model():
activity = Activity(
id="act1",
user_id="123",
garmin_activity_id="gact1",
activity_type="running",
start_time=datetime.now(),
)
assert activity.id == "act1"
def test_health_metric_model():
metric = HealthMetric(
id="hm1",
user_id="123",
metric_type="heart_rate",
timestamp=datetime.now(),
value=70.5,
)
assert metric.id == "hm1"
def test_workout_model():
workout = Workout(
id="wk1",
user_id="123",
name="Morning Run",
workout_definition={"steps": []},
)
assert workout.id == "wk1"

View File

@@ -3,8 +3,8 @@ from unittest.mock import AsyncMock, patch
import pytest
from src.schemas import GarminCredentials
from src.services.garmin_auth_service import GarminAuthService
from backend.src.models.central_db_models import GarminCredentials
from backend.src.services.garmin_auth_service import GarminAuthService
@pytest.fixture
@@ -17,7 +17,7 @@ async def test_initial_login_success(garmin_auth_service):
username = "test@example.com"
password = "password123"
with patch("src.services.garmin_auth_service.garth") as mock_garth:
with patch("garth") as mock_garth:
mock_garth.Client.return_value = AsyncMock()
mock_garth.Client.return_value.login.return_value = (
None # garth.login doesn't return anything directly
@@ -47,7 +47,7 @@ async def test_initial_login_failure(garmin_auth_service):
username = "invalid@example.com"
password = "wrongpassword"
with patch("backend.src.services.garmin_auth_service.garth") as mock_garth:
with patch("garth") as mock_garth:
mock_garth.Client.return_value = AsyncMock()
mock_garth.Client.return_value.login.side_effect = Exception(
"Garmin login failed"
@@ -68,7 +68,7 @@ async def test_refresh_tokens_success(garmin_auth_service):
token_expiration_date=datetime.utcnow() - timedelta(minutes=1), # Expired token
)
with patch("backend.src.services.garmin_auth_service.garth") as mock_garth:
with patch("garth") as mock_garth:
mock_garth.Client.return_value = AsyncMock()
mock_garth.Client.return_value.reauthorize.return_value = None
mock_garth.Client.return_value.access_token = "refreshed_access_token"
@@ -99,7 +99,7 @@ async def test_refresh_tokens_failure(garmin_auth_service):
token_expiration_date=datetime.utcnow() - timedelta(minutes=1),
)
with patch("backend.src.services.garmin_auth_service.garth") as mock_garth:
with patch("garth") as mock_garth:
mock_garth.Client.return_value = AsyncMock()
mock_garth.Client.return_value.reauthorize.side_effect = Exception(
"Garmin reauthorize failed"

View File

@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
import pytest
from fastapi import HTTPException
from src.services.rate_limiter import RateLimiter
from backend.src.services.rate_limiter import RateLimiter
@pytest.mark.asyncio

View File

@@ -1,5 +1,16 @@
import pytest
from backend.src.services.sync_manager import CurrentSyncJobManager
from backend.src.services.sync_manager import (
CurrentSyncJobManager,
current_sync_job_manager,
)
@pytest.fixture(autouse=True)
async def reset_sync_manager_state():
"""Resets the singleton instance's state before each test."""
current_sync_job_manager._current_job = None
yield
@pytest.mark.asyncio
@@ -44,3 +55,30 @@ async def test_fail_sync():
status = await manager.get_current_sync_status()
assert status.status == "failed"
assert status.error_message == "Test error"
@pytest.mark.asyncio
async def test_cancel_sync():
manager = CurrentSyncJobManager()
await manager.start_sync("activities")
await manager.cancel_sync()
status = await manager.get_current_sync_status()
assert status.status == "cancelled"
assert status.cancellation_requested is True
assert status.end_time is not None
@pytest.mark.asyncio
async def test_cancel_sync_when_not_active():
manager = CurrentSyncJobManager()
# No sync started
await manager.cancel_sync()
status = await manager.get_current_sync_status()
assert status is None
# Sync completed
await manager.start_sync("activities")
await manager.complete_sync()
await manager.cancel_sync()
status = await manager.get_current_sync_status()
assert status.status == "completed" # Should not change after completion