from unittest.mock import patch import pytest from fastapi.testclient import TestClient from main import app client = TestClient(app, raise_server_exceptions=False) @pytest.fixture def mock_sync(): with patch("main.GarminSync") as mock: yield mock @pytest.fixture def mock_engine(): with patch("main.RecommendationEngine") as mock: yield mock @pytest.fixture def mock_settings_manager(): with patch("main.SettingsManager") as mock: yield mock @pytest.fixture def mock_workout_manager(): with patch("main.WorkoutManager") as mock: yield mock def test_health(): response = client.get("/health") assert response.status_code == 200 assert response.json() == {"status": "ok"} def test_get_activities_success(mock_sync): mock_instance = mock_sync.return_value mock_instance.load_local_activities.return_value = [{"activityId": 1}] response = client.get("/activities") assert response.status_code == 200 assert response.json() == [{"activityId": 1}] def test_get_activities_error(mock_sync): mock_instance = mock_sync.return_value mock_instance.load_local_activities.side_effect = Exception("Load failed") response = client.get("/activities") assert response.status_code == 500 assert "INTERNAL_SERVER_ERROR" in response.json()["error"] def test_get_recommendation_success(mock_sync, mock_engine, monkeypatch): monkeypatch.setenv("GEMINI_API_KEY", "test-key") mock_sync_instance = mock_sync.return_value mock_sync_instance.load_local_activities.return_value = [] mock_engine_instance = mock_engine.return_value mock_engine_instance.get_recommendation.return_value = "Great job!" response = client.get("/recommendation") assert response.status_code == 200 assert response.json() == {"recommendation": "Great job!"} def test_get_recommendation_missing_key(mock_sync, mock_engine, monkeypatch): monkeypatch.setenv("GEMINI_API_KEY", "") response = client.get("/recommendation") assert "not configured" in response.json()["recommendation"] def test_settings_status(): with patch("main.env") as mock_env: mock_env.get_status.return_value = {"configured": True} response = client.get("/settings") assert response.status_code == 200 assert "garmin" in response.json() def test_settings_status_v2(): response = client.get("/settings/status") assert response.status_code == 200 assert "garmin" in response.json() def test_update_settings_garmin(): with patch("main.env") as mock_env: response = client.post("/settings/garmin", json={"email": "a", "password": "b"}) assert response.status_code == 200 mock_env.set_credentials.assert_called_once() def test_update_settings_garmin_missing(): response = client.post("/settings/garmin", json={}) assert response.status_code == 400 def test_update_settings_withings(): with patch("main.env") as mock_env: response = client.post("/settings/withings", json={"client_id": "a", "client_secret": "b"}) assert response.status_code == 200 mock_env.set_credentials.assert_called_once() def test_update_settings_gemini(): with patch("main.env") as mock_env: response = client.post("/settings/gemini", json={"api_key": "a"}) assert response.status_code == 200 mock_env.set_credentials.assert_called_once() def test_auth_status_success(monkeypatch): monkeypatch.setenv("GARMIN_EMAIL", "test@test.com") with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" response = client.get("/auth/status") assert response.json()["authenticated"] is True def test_login_mfa(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "MFA_REQUIRED" response = client.post("/auth/login", json={"email": "a", "password": "b"}) assert response.json()["status"] == "MFA_REQUIRED" def test_sync_smart(mock_sync): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_sync.return_value.sync_smart.return_value = 10 response = client.post("/sync/smart") assert response.json()["synced_count"] == 10 def test_sync_smart_fail(mock_sync): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.post("/sync/smart") assert response.json()["success"] is False def test_analyze_stats(mock_sync): mock_sync.return_value.get_weekly_stats.return_value = {"labels": ["W1"]} response = client.get("/analyze/stats") assert response.json()["weekly"]["labels"] == ["W1"] def test_analyze_stats_error(mock_sync): mock_sync.return_value.get_weekly_stats.side_effect = Exception("Err") response = client.get("/analyze/stats") assert "weekly" in response.json() def test_profile(mock_settings_manager): mock_settings_manager.return_value.load_profile.return_value = {"name": "Test"} response = client.get("/settings/profile") assert response.json()["name"] == "Test" response = client.post("/settings/profile", json={"name": "New"}) assert response.json()["status"] == "SUCCESS" def test_analyze_chat(mock_engine, monkeypatch): monkeypatch.setenv("GEMINI_API_KEY", "k") mock_engine.return_value.chat_with_data.return_value = "Hello" response = client.post("/analyze/chat", json={"message": "hi"}) assert response.json()["message"] == "Hello" def test_workouts_list(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_client.return_value.get_workouts_list.return_value = [] response = client.get("/workouts") assert response.status_code == 200 def test_workouts_chat(mock_workout_manager): # main.py line 289 returns {"workout": workout} mock_workout_manager.return_value.generate_workout_json.return_value = {"ok": True} response = client.post("/workouts/chat", json={"prompt": "test"}) assert response.json()["workout"]["ok"] is True def test_workout_constants(mock_workout_manager): mock_workout_manager.return_value.get_constants.return_value = {"C": 1} response = client.get("/workouts/constants") assert response.json()["C"] == 1 def test_workout_upload(mock_workout_manager): mock_workout_manager.return_value.validate_workout_json.return_value = [] with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_client.return_value.upload_workout.return_value = {"id": 1} response = client.post("/workouts/upload", json={"name": "W"}) assert response.json()["success"] is True def test_update_settings_withings_missing(): response = client.post("/settings/withings", json={}) assert response.status_code == 400 def test_update_settings_gemini_missing(): response = client.post("/settings/gemini", json={}) assert response.status_code == 400 def test_auth_status_not_configured(monkeypatch): # Mock load_service_env to prevent reloading from file with patch("main.env.load_service_env"): monkeypatch.setenv("GARMIN_EMAIL", "") response = client.get("/auth/status") assert response.json()["authenticated"] is False def test_sync_full_success(mock_sync): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_sync.return_value.sync_activities.return_value = 100 response = client.post("/sync/full") assert response.json()["synced_count"] == 100 def test_sync_full_fail(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.post("/sync/full") assert response.status_code == 401 def test_sync_smart_error(mock_sync): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_sync.return_value.sync_smart.side_effect = Exception("Smart fail") response = client.post("/sync/smart") assert response.json()["success"] is False def test_workouts_list_fail(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.get("/workouts") assert response.status_code == 401 def test_workouts_chat_error(mock_workout_manager): mock_workout_manager.return_value.generate_workout_json.side_effect = Exception("AI Fail") response = client.post("/workouts/chat", json={"prompt": "test"}) assert "AI Fail" in response.json()["error"] def test_dashboard_stats_error(mock_sync): mock_sync.return_value.get_dashboard_stats.side_effect = Exception("Dash fail") response = client.get("/analyze/dashboard") assert "Dash fail" in response.json()["error"] def test_workout_validate_invalid(mock_workout_manager): mock_workout_manager.return_value.validate_workout_json.return_value = ["Error"] response = client.post("/workouts/validate", json={}) assert response.json()["valid"] is False def test_workout_upload_fail_validate(mock_workout_manager): mock_workout_manager.return_value.validate_workout_json.return_value = ["Error"] response = client.post("/workouts/upload", json={}) assert response.json()["success"] is False def test_workout_upload_fail_login(mock_workout_manager): mock_workout_manager.return_value.validate_workout_json.return_value = [] with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.post("/workouts/upload", json={}) assert response.json()["success"] is False def test_workout_upload_exception(mock_workout_manager): mock_workout_manager.return_value.validate_workout_json.return_value = [] with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_client.return_value.upload_workout.side_effect = Exception("Upload fail") response = client.post("/workouts/upload", json={}) assert response.json()["success"] is False def test_login_save_credentials(monkeypatch): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" with patch("main.env") as mock_env: response = client.post("/auth/login", json={"email": "new@test.com", "password": "new"}) assert response.status_code == 200 mock_env.set_credentials.assert_called_once() def test_trigger_sync_endpoint_success(mock_sync): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "SUCCESS" mock_sync.return_value.sync_activities.return_value = 5 response = client.post("/sync") assert response.json()["synced_count"] == 5 def test_trigger_sync_endpoint_fail(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.post("/sync") assert response.status_code == 401 def test_login_credentials_missing(monkeypatch): with patch("main.env.load_service_env"): monkeypatch.setenv("GARMIN_EMAIL", "") monkeypatch.setenv("GARMIN_PASSWORD", "") response = client.post("/auth/login", json={"email": ""}) assert response.status_code == 400 def test_login_failed_error(): with patch("main.GarminClient") as mock_client: mock_client.return_value.login.return_value = "FAILURE" response = client.post("/auth/login", json={"email": "a", "password": "b"}) assert response.status_code == 401