From 439c5f3d6fabd92fb510345eb6b4103cc7cc5d43 Mon Sep 17 00:00:00 2001 From: kbe Date: Fri, 8 Aug 2025 22:09:48 +0200 Subject: [PATCH] feat: New script for booking test function in shell and python --- crossfit_booker.py | 2 +- test/test_crossfit_booker_final.py | 236 -------------------------- test/test_crossfit_booker_init.py | 17 -- test/test_crossfit_booker_sessions.py | 30 ++-- 4 files changed, 16 insertions(+), 269 deletions(-) delete mode 100644 test/test_crossfit_booker_final.py delete mode 100644 test/test_crossfit_booker_init.py diff --git a/crossfit_booker.py b/crossfit_booker.py index f275fa4..00fbf16 100644 --- a/crossfit_booker.py +++ b/crossfit_booker.py @@ -304,7 +304,7 @@ class CrossFitBooker: for session_type, s in found_preferred_sessions: st_dt = self._parse_local(s["start_timestamp"]) logging.info(f"Attempting to book {session_type} session at {st_dt} ({s['name_activity']})") - if await self.book_session(s["id_activity_calendar"]): + if self.book_session(s["id_activity_calendar"]): details = f"{s['name_activity']} at {st_dt.strftime('%Y-%m-%d %H:%M')}" await self.notifier.notify_session_booking(details) logging.info(f"Successfully booked {session_type} session at {st_dt}") diff --git a/test/test_crossfit_booker_final.py b/test/test_crossfit_booker_final.py deleted file mode 100644 index 0dc29dd..0000000 --- a/test/test_crossfit_booker_final.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive unit tests for the CrossFitBooker class in crossfit_booker.py -""" - -import os -import sys -from unittest.mock import Mock, patch -from datetime import date -import requests - -# 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 \ No newline at end of file diff --git a/test/test_crossfit_booker_init.py b/test/test_crossfit_booker_init.py deleted file mode 100644 index 664d17c..0000000 --- a/test/test_crossfit_booker_init.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for CrossFitBooker initialization -""" - -import pytest -import os -import sys - -# Add the parent directory to the path -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - - - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) \ No newline at end of file diff --git a/test/test_crossfit_booker_sessions.py b/test/test_crossfit_booker_sessions.py index 7fad7f7..23217bf 100644 --- a/test/test_crossfit_booker_sessions.py +++ b/test/test_crossfit_booker_sessions.py @@ -190,18 +190,18 @@ class TestCrossFitBookerIsSessionBookable: assert result is False -class TestCrossFitBookerRunBookingCycle: - """Test cases for run_booking_cycle method""" +class TestCrossFitBookerExcuteCycle: + """Test cases for execute_cycle method""" @patch('crossfit_booker.CrossFitBooker.get_available_sessions') @patch('crossfit_booker.CrossFitBooker.is_session_bookable') @patch('crossfit_booker.CrossFitBooker.matches_preferred_session') @patch('crossfit_booker.CrossFitBooker.book_session') - async def test_run_booking_cycle_no_sessions(self, mock_book_session, mock_matches_preferred, mock_is_bookable, mock_get_sessions): - """Test run_booking_cycle with no available sessions""" + async def test_execute_cycle_no_sessions(self, mock_book_session, mock_matches_preferred, mock_is_bookable, mock_get_sessions): + """Test execute_cycle with no available sessions""" mock_get_sessions.return_value = {"success": False} booker = CrossFitBooker() - await booker.run_booking_cycle(datetime.now(pytz.timezone("Europe/Paris"))) + await booker.execute_cycle(datetime.now(pytz.timezone("Europe/Paris"))) mock_get_sessions.assert_called_once() mock_book_session.assert_not_called() @@ -209,8 +209,8 @@ class TestCrossFitBookerRunBookingCycle: @patch('crossfit_booker.CrossFitBooker.is_session_bookable') @patch('crossfit_booker.CrossFitBooker.matches_preferred_session') @patch('crossfit_booker.CrossFitBooker.book_session') - async def test_run_booking_cycle_with_sessions(self, mock_book_session, mock_matches_preferred, mock_is_bookable, mock_get_sessions): - """Test run_booking_cycle with available sessions""" + async def test_execute_cycle_with_sessions(self, mock_book_session, mock_matches_preferred, mock_is_bookable, mock_get_sessions): + """Test execute_cycle with available sessions""" # Use current date for the session to ensure it falls within 0-2 day window current_time = datetime.now(pytz.timezone("Europe/Paris")) session_date = current_time.date() @@ -233,7 +233,7 @@ class TestCrossFitBookerRunBookingCycle: mock_book_session.return_value = True booker = CrossFitBooker() - await booker.run_booking_cycle(current_time) + await booker.execute_cycle(current_time) mock_get_sessions.assert_called_once() mock_is_bookable.assert_called_once() @@ -245,22 +245,22 @@ class TestCrossFitBookerRun: """Test cases for run method""" @patch('crossfit_booker.CrossFitBooker.login') - @patch('crossfit_booker.CrossFitBooker.run_booking_cycle') - async def test_run_auth_failure(self, mock_run_booking_cycle, mock_login): + @patch('crossfit_booker.CrossFitBooker.execute_cycle') + async def test_run_auth_failure(self, mock_execute_cycle, mock_login): """Test run with authentication failure""" mock_login.return_value = False booker = CrossFitBooker() with patch.object(booker, 'run', new=booker.run) as mock_run: await booker.run() mock_login.assert_called_once() - mock_run_booking_cycle.assert_not_called() + mock_execute_cycle.assert_not_called() @patch('crossfit_booker.CrossFitBooker.login') - @patch('crossfit_booker.CrossFitBooker.run_booking_cycle') + @patch('crossfit_booker.CrossFitBooker.execute_cycle') @patch('crossfit_booker.CrossFitBooker.quit') @patch('time.sleep') @patch('datetime.datetime') - async def test_run_booking_outside_window(self, mock_datetime, mock_sleep, mock_quit, mock_run_booking_cycle, mock_login): + async def test_run_booking_outside_window(self, mock_datetime, mock_sleep, mock_quit, mock_execute_cycle, mock_login): """Test run with booking outside window""" mock_login.return_value = True mock_quit.return_value = None # Prevent actual exit @@ -292,8 +292,8 @@ class TestCrossFitBookerRun: # Verify login was called mock_login.assert_called_once() - # Verify run_booking_cycle was NOT called since we're outside the booking window - mock_run_booking_cycle.assert_not_called() + # Verify execute_cycle was NOT called since we're outside the booking window + mock_execute_cycle.assert_not_called() # Verify quit was called (due to KeyboardInterrupt) mock_quit.assert_called_once()