Implement CLI app for API interaction with MFA

- Create full CLI application with authentication, sync triggering, and status checking
- Implement MFA support for secure authentication
- Add token management with secure local storage
- Create API client for backend communication
- Implement data models for User Session, Sync Job, and Authentication Token
- Add command-line interface with auth and sync commands
- Include unit and integration tests
- Follow project constitution standards for Python 3.13, type hints, and code quality
- Support multiple output formats (table, JSON, CSV)
This commit is contained in:
2025-12-18 15:23:56 -08:00
parent 31f96660c7
commit fb6417b1a3
27 changed files with 1502 additions and 44 deletions

View File

@@ -0,0 +1,176 @@
import pytest
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from src.api.client import ApiClient
from src.auth.token_manager import TokenManager
from src.auth.auth_manager import AuthManager
@pytest.mark.asyncio
@patch('src.api.client.httpx.AsyncClient')
async def test_sync_trigger_integration(mock_http_client):
"""Integration test for sync trigger functionality"""
# Mock the HTTP client response for sync trigger
mock_response = AsyncMock()
mock_response.json.return_value = {
"success": True,
"job_id": "job123",
"status": "pending",
"message": "Sync job created successfully"
}
mock_response.raise_for_status = MagicMock()
# Setup the mock client
mock_instance = MagicMock()
mock_instance.post.return_value = mock_response
mock_instance.headers = {}
mock_http_client.return_value = mock_instance
# Create instances with mocked file operations
api_client = ApiClient(base_url="https://test-api.garmin.com")
token_manager = TokenManager()
# Mock token manager methods
token_manager.load_token = MagicMock(return_value=MagicMock(
access_token="test_token",
token_type="Bearer"
))
token_manager.token_exists = MagicMock(return_value=True)
auth_manager = AuthManager(api_client, token_manager)
# Mock the auth manager to simulate authentication
original_is_authenticated = auth_manager.is_authenticated
async def mock_is_authenticated():
return True
auth_manager.is_authenticated = mock_is_authenticated
# Test sync trigger
result = await api_client.trigger_sync("activities", {"start_date": "2023-01-01", "end_date": "2023-01-31"}, False)
assert result["success"] is True
assert result["job_id"] == "job123"
assert result["status"] == "pending"
await api_client.close()
@pytest.mark.asyncio
@patch('src.api.client.httpx.AsyncClient')
async def test_sync_status_integration(mock_http_client):
"""Integration test for sync status functionality"""
# Mock the HTTP client response for sync status
mock_response = AsyncMock()
mock_response.json.return_value = {
"success": True,
"jobs": [
{
"job_id": "job123",
"status": "completed",
"progress": 100.0,
"sync_type": "activities",
"created_at": "2023-01-01T10:00:00Z",
"start_time": "2023-01-01T10:01:00Z",
"end_time": "2023-01-01T10:05:00Z",
"total_items": 10,
"processed_items": 10
}
]
}
mock_response.raise_for_status = MagicMock()
# Setup the mock client
mock_instance = MagicMock()
mock_instance.get.return_value = mock_response
mock_instance.headers = {}
mock_http_client.return_value = mock_instance
# Create instances with mocked file operations
api_client = ApiClient(base_url="https://test-api.garmin.com")
token_manager = TokenManager()
# Mock token manager methods
token_manager.load_token = MagicMock(return_value=MagicMock(
access_token="test_token",
token_type="Bearer"
))
token_manager.token_exists = MagicMock(return_value=True)
auth_manager = AuthManager(api_client, token_manager)
# Mock the auth manager to simulate authentication
async def mock_is_authenticated():
return True
auth_manager.is_authenticated = mock_is_authenticated
# Test sync status
result = await api_client.get_sync_status()
assert result["success"] is True
jobs = result["jobs"]
assert len(jobs) == 1
assert jobs[0]["job_id"] == "job123"
assert jobs[0]["status"] == "completed"
await api_client.close()
@pytest.mark.asyncio
@patch('src.api.client.httpx.AsyncClient')
async def test_sync_status_single_job_integration(mock_http_client):
"""Integration test for sync status for a specific job"""
# Mock the HTTP client response for single job status
mock_response = AsyncMock()
mock_response.json.return_value = {
"success": True,
"job": {
"job_id": "job456",
"status": "running",
"progress": 50.0,
"sync_type": "health",
"created_at": "2023-01-01T10:00:00Z",
"start_time": "2023-01-01T10:01:00Z",
"total_items": 100,
"processed_items": 50
}
}
mock_response.raise_for_status = MagicMock()
# Setup the mock client
mock_instance = MagicMock()
mock_instance.get.return_value = mock_response
mock_instance.headers = {}
mock_http_client.return_value = mock_instance
# Create instances with mocked file operations
api_client = ApiClient(base_url="https://test-api.garmin.com")
token_manager = TokenManager()
# Mock token manager methods
token_manager.load_token = MagicMock(return_value=MagicMock(
access_token="test_token",
token_type="Bearer"
))
token_manager.token_exists = MagicMock(return_value=True)
auth_manager = AuthManager(api_client, token_manager)
# Mock the auth manager to simulate authentication
async def mock_is_authenticated():
return True
auth_manager.is_authenticated = mock_is_authenticated
# Test sync status for specific job
result = await api_client.get_sync_status("job456")
assert result["success"] is True
job = result["job"]
assert job["job_id"] == "job456"
assert job["status"] == "running"
assert job["progress"] == 50.0
await api_client.close()