#!/usr/bin/env python3 """ Comprehensive unit tests for the CrossFitBooker class in crossfit_booker.py """ import pytest import os import sys from unittest.mock import Mock, patch, MagicMock, AsyncMock from datetime import datetime, date, timedelta import pytz import requests from typing import Dict, Any, List # Add the parent directory to the path to import crossfit_booker sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from crossfit_booker import CrossFitBooker class TestCrossFitBookerInit: """Test cases for CrossFitBooker initialization""" def test_init_success(self): """Test successful initialization with all required env vars""" with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass', 'EMAIL_FROM': 'from@test.com', 'EMAIL_TO': 'to@test.com', 'EMAIL_PASSWORD': 'email_pass', 'TELEGRAM_TOKEN': 'telegram_token', 'TELEGRAM_CHAT_ID': '12345' }): booker = CrossFitBooker() assert booker.auth_token is None assert booker.user_id is None assert booker.session is not None assert booker.notifier is not None def test_init_missing_credentials(self): """Test initialization fails with missing credentials""" with patch.dict(os.environ, {}, clear=True): try: CrossFitBooker() except ValueError as e: assert str(e) == "Missing environment variables" def test_init_partial_credentials(self): """Test initialization fails with partial credentials""" with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user' # Missing PASSWORD }, clear=True): try: CrossFitBooker() except ValueError as e: assert str(e) == "Missing environment variables" class TestCrossFitBookerAuthHeaders: """Test cases for get_auth_headers method""" def test_get_auth_headers_without_token(self): """Test headers without auth token""" with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() headers = booker.get_auth_headers() assert "Authorization" not in headers assert headers["User-Agent"] == "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0" def test_get_auth_headers_with_token(self): """Test headers with auth token""" with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() booker.auth_token = "test_token_123" headers = booker.get_auth_headers() assert headers["Authorization"] == "Bearer test_token_123" class TestCrossFitBookerLogin: """Test cases for login method""" @patch('requests.Session.post') def test_login_success(self, mock_post): """Test successful login flow""" # Mock first login response mock_response1 = Mock() mock_response1.ok = True mock_response1.json.return_value = { "data": { "user": { "id_user": "12345" } } } # Mock second login response mock_response2 = Mock() mock_response2.ok = True mock_response2.json.return_value = { "token": "test_bearer_token" } mock_post.side_effect = [mock_response1, mock_response2] with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() result = booker.login() assert result is True assert booker.user_id == "12345" assert booker.auth_token == "test_bearer_token" @patch('requests.Session.post') def test_login_first_step_failure(self, mock_post): """Test login failure on first step""" mock_response = Mock() mock_response.ok = False mock_response.status_code = 400 mock_response.text = "Bad Request" mock_post.return_value = mock_response with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() result = booker.login() assert result is False assert booker.user_id is None assert booker.auth_token is None @patch('requests.Session.post') def test_login_json_parsing_error(self, mock_post): """Test login with JSON parsing error""" mock_response = Mock() mock_response.ok = True mock_response.json.side_effect = ValueError("Invalid JSON") mock_post.return_value = mock_response with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() result = booker.login() assert result is False @patch('requests.Session.post') def test_login_request_exception(self, mock_post): """Test login with request exception""" mock_post.side_effect = requests.exceptions.ConnectionError("Network error") with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() result = booker.login() assert result is False class TestCrossFitBookerGetAvailableSessions: """Test cases for get_available_sessions method""" def test_get_available_sessions_no_auth(self): """Test get_available_sessions without authentication""" with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() result = booker.get_available_sessions(date(2025, 7, 24), date(2025, 7, 25)) assert result is None @patch('requests.Session.post') def test_get_available_sessions_success(self, mock_post): """Test successful get_available_sessions""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "success": True, "data": { "activities_calendar": [ {"id": "1", "name": "Test Session"} ] } } mock_post.return_value = mock_response with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() booker.auth_token = "test_token" booker.user_id = "12345" result = booker.get_available_sessions(date(2025, 7, 24), date(2025, 7, 25)) assert result is not None assert result["success"] is True @patch('requests.Session.post') def test_get_available_sessions_failure(self, mock_post): """Test get_available_sessions with API failure""" mock_response = Mock() mock_response.status_code = 400 mock_response.text = "Bad Request" mock_post.return_value = mock_response with patch.dict(os.environ, { 'CROSSFIT_USERNAME': 'test_user', 'CROSSFIT_PASSWORD': 'test_pass' }): booker = CrossFitBooker() booker.auth_token = "test_token" booker.user_id = "12345" result = booker.get_available_sessions(date(2025, 7, 24), date(2025, 7, 25)) assert result is None