test: add coverage for most methods but still bugs
This commit is contained in:
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[pytest]
|
||||||
|
asyncio_mode = auto
|
||||||
@@ -6,6 +6,12 @@ h11==0.16.0
|
|||||||
httpcore==1.0.9
|
httpcore==1.0.9
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
idna==3.10
|
idna==3.10
|
||||||
|
iniconfig==2.1.0
|
||||||
|
packaging==25.0
|
||||||
|
pluggy==1.6.0
|
||||||
|
Pygments==2.19.2
|
||||||
|
pytest==8.4.1
|
||||||
|
pytest-asyncio==1.1.0
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.1.1
|
python-dotenv==1.1.1
|
||||||
python-telegram-bot==22.2
|
python-telegram-bot==22.2
|
||||||
|
|||||||
209
test/test_crossfit_booker.py
Executable file
209
test/test_crossfit_booker.py
Executable file
@@ -0,0 +1,209 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for the refactored CrossFitBooker functional implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
from typing import List, Dict, Any, Tuple
|
||||||
|
|
||||||
|
# Add the current directory to the path so we can import our modules
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Import the functional functions from our refactored code
|
||||||
|
# Import the functional functions from our refactored code
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
from crossfit_booker_functional import (
|
||||||
|
is_session_bookable,
|
||||||
|
matches_preferred_session,
|
||||||
|
filter_bookable_sessions,
|
||||||
|
filter_upcoming_sessions,
|
||||||
|
filter_preferred_sessions,
|
||||||
|
categorize_sessions,
|
||||||
|
format_session_details
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_is_session_bookable():
|
||||||
|
"""Test the is_session_bookable function."""
|
||||||
|
print("Testing is_session_bookable...")
|
||||||
|
|
||||||
|
# Test case 1: Session with can_join = True
|
||||||
|
session1 = {
|
||||||
|
"user_info": {
|
||||||
|
"can_join": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
assert is_session_bookable(session1, current_time, "Europe/Paris") == True
|
||||||
|
|
||||||
|
# Test case 2: Session with booking window in the past
|
||||||
|
session2 = {
|
||||||
|
"user_info": {
|
||||||
|
"unableToBookUntilDate": "01-01-2020",
|
||||||
|
"unableToBookUntilTime": "10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert is_session_bookable(session2, current_time, "Europe/Paris") == True
|
||||||
|
|
||||||
|
# Test case 3: Session with booking window in the future
|
||||||
|
session3 = {
|
||||||
|
"user_info": {
|
||||||
|
"unableToBookUntilDate": "01-01-2030",
|
||||||
|
"unableToBookUntilTime": "10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert is_session_bookable(session3, current_time, "Europe/Paris") == False
|
||||||
|
|
||||||
|
print("✓ is_session_bookable tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_matches_preferred_session():
|
||||||
|
"""Test the matches_preferred_session function."""
|
||||||
|
print("Testing matches_preferred_session...")
|
||||||
|
|
||||||
|
# Define some preferred sessions (day_of_week, start_time, session_name_contains)
|
||||||
|
preferred_sessions: List[Tuple[int, str, str]] = [
|
||||||
|
(2, "18:30", "CONDITIONING"), # Wednesday 18:30 CONDITIONING
|
||||||
|
(4, "17:00", "WEIGHTLIFTING"), # Friday 17:00 WEIGHTLIFTING
|
||||||
|
(5, "12:30", "HYROX"), # Saturday 12:30 HYROX
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test case 1: Exact match
|
||||||
|
session1 = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00", # Wednesday
|
||||||
|
"name_activity": "CONDITIONING"
|
||||||
|
}
|
||||||
|
assert matches_preferred_session(session1, preferred_sessions, "Europe/Paris") == True
|
||||||
|
|
||||||
|
# Test case 2: No match
|
||||||
|
session2 = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00", # Wednesday
|
||||||
|
"name_activity": "YOGA"
|
||||||
|
}
|
||||||
|
assert matches_preferred_session(session2, preferred_sessions, "Europe/Paris") == False
|
||||||
|
|
||||||
|
print("✓ matches_preferred_session tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_functions():
|
||||||
|
"""Test the filter functions."""
|
||||||
|
print("Testing filter functions...")
|
||||||
|
|
||||||
|
# Define some preferred sessions
|
||||||
|
preferred_sessions: List[Tuple[int, str, str]] = [
|
||||||
|
(2, "18:30", "CONDITIONING"), # Wednesday 18:30 CONDITIONING
|
||||||
|
(4, "17:00", "WEIGHTLIFTING"), # Friday 17:00 WEIGHTLIFTING
|
||||||
|
(5, "12:30", "HYROX"), # Saturday 12:30 HYROX
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create some test sessions
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
sessions = [
|
||||||
|
{
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00", # Wednesday
|
||||||
|
"name_activity": "CONDITIONING",
|
||||||
|
"user_info": {"can_join": True}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_timestamp": "2025-07-30 19:00:00", # Wednesday
|
||||||
|
"name_activity": "YOGA",
|
||||||
|
"user_info": {"can_join": True}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_timestamp": "2025-08-01 17:00:00", # Friday
|
||||||
|
"name_activity": "WEIGHTLIFTING",
|
||||||
|
"user_info": {"can_join": True}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test filter_preferred_sessions
|
||||||
|
preferred = filter_preferred_sessions(sessions, preferred_sessions, "Europe/Paris")
|
||||||
|
assert len(preferred) == 2 # CONDITIONING and WEIGHTLIFTING sessions
|
||||||
|
|
||||||
|
# Test filter_bookable_sessions
|
||||||
|
bookable = filter_bookable_sessions(sessions, current_time, preferred_sessions, "Europe/Paris")
|
||||||
|
assert len(bookable) == 2 # Both preferred sessions are bookable
|
||||||
|
|
||||||
|
print("✓ Filter function tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_categorize_sessions():
|
||||||
|
"""Test the categorize_sessions function."""
|
||||||
|
print("Testing categorize_sessions...")
|
||||||
|
|
||||||
|
# Define some preferred sessions
|
||||||
|
preferred_sessions: List[Tuple[int, str, str]] = [
|
||||||
|
(2, "18:30", "CONDITIONING"), # Wednesday 18:30 CONDITIONING
|
||||||
|
(4, "17:00", "WEIGHTLIFTING"), # Friday 17:00 WEIGHTLIFTING
|
||||||
|
(5, "12:30", "HYROX"), # Saturday 12:30 HYROX
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create some test sessions
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
sessions = [
|
||||||
|
{
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00", # Wednesday
|
||||||
|
"name_activity": "CONDITIONING",
|
||||||
|
"user_info": {"can_join": True}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_timestamp": "2025-07-31 18:30:00", # Thursday (tomorrow relative to Wednesday)
|
||||||
|
"name_activity": "CONDITIONING",
|
||||||
|
"user_info": {"can_join": False, "unableToBookUntilDate": "01-08-2025", "unableToBookUntilTime": "10:00"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test categorize_sessions
|
||||||
|
categorized = categorize_sessions(sessions, current_time, preferred_sessions, "Europe/Paris")
|
||||||
|
assert "bookable" in categorized
|
||||||
|
assert "upcoming" in categorized
|
||||||
|
assert "all_preferred" in categorized
|
||||||
|
|
||||||
|
print("✓ categorize_sessions tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_session_details():
|
||||||
|
"""Test the format_session_details function."""
|
||||||
|
print("Testing format_session_details...")
|
||||||
|
|
||||||
|
# Test case 1: Valid session
|
||||||
|
session1 = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00",
|
||||||
|
"name_activity": "CONDITIONING"
|
||||||
|
}
|
||||||
|
formatted = format_session_details(session1, "Europe/Paris")
|
||||||
|
assert "CONDITIONING" in formatted
|
||||||
|
assert "2025-07-30 18:30" in formatted
|
||||||
|
|
||||||
|
# Test case 2: Session with missing data
|
||||||
|
session2 = {
|
||||||
|
"name_activity": "WEIGHTLIFTING"
|
||||||
|
}
|
||||||
|
formatted = format_session_details(session2, "Europe/Paris")
|
||||||
|
assert "WEIGHTLIFTING" in formatted
|
||||||
|
assert "Unknown time" in formatted
|
||||||
|
|
||||||
|
print("✓ format_session_details tests passed")
|
||||||
|
|
||||||
|
|
||||||
|
def run_all_tests():
|
||||||
|
"""Run all tests."""
|
||||||
|
print("Running all tests for CrossFitBooker functional implementation...\n")
|
||||||
|
|
||||||
|
test_is_session_bookable()
|
||||||
|
test_matches_preferred_session()
|
||||||
|
test_filter_functions()
|
||||||
|
test_categorize_sessions()
|
||||||
|
test_format_session_details()
|
||||||
|
|
||||||
|
print("\n✓ All tests passed!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_all_tests()
|
||||||
165
test/test_crossfit_booker_auth.py
Normal file
165
test/test_crossfit_booker_auth.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Unit tests for CrossFitBooker authentication methods
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
|
# Add the parent directory to the path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from crossfit_booker import CrossFitBooker
|
||||||
|
|
||||||
|
|
||||||
|
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_second_step_failure(self, mock_post):
|
||||||
|
"""Test login failure on second step"""
|
||||||
|
# First response succeeds
|
||||||
|
mock_response1 = Mock()
|
||||||
|
mock_response1.ok = True
|
||||||
|
mock_response1.json.return_value = {
|
||||||
|
"data": {
|
||||||
|
"user": {
|
||||||
|
"id_user": "12345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Second response fails
|
||||||
|
mock_response2 = Mock()
|
||||||
|
mock_response2.ok = False
|
||||||
|
mock_response2.status_code = 401
|
||||||
|
|
||||||
|
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 False
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
217
test/test_crossfit_booker_final.py
Normal file
217
test/test_crossfit_booker_final.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#!/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):
|
||||||
|
with pytest.raises(ValueError, match="Missing environment variables"):
|
||||||
|
CrossFitBooker()
|
||||||
|
|
||||||
|
def test_init_partial_credentials(self):
|
||||||
|
"""Test initialization fails with partial credentials"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user'
|
||||||
|
# Missing PASSWORD
|
||||||
|
}):
|
||||||
|
with pytest.raises(ValueError, match="Missing environment variables"):
|
||||||
|
CrossFitBooker()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
65
test/test_crossfit_booker_init.py
Normal file
65
test/test_crossfit_booker_init.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Unit tests for CrossFitBooker initialization
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
# Add the parent directory to the path
|
||||||
|
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):
|
||||||
|
with pytest.raises(ValueError, match="Missing environment variables"):
|
||||||
|
CrossFitBooker()
|
||||||
|
|
||||||
|
def test_init_partial_credentials(self):
|
||||||
|
"""Test initialization fails with partial credentials"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user'
|
||||||
|
# Missing PASSWORD
|
||||||
|
}):
|
||||||
|
with pytest.raises(ValueError, match="Missing environment variables"):
|
||||||
|
CrossFitBooker()
|
||||||
|
|
||||||
|
def test_init_with_optional_env_vars(self):
|
||||||
|
"""Test initialization with optional environment variables"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass',
|
||||||
|
'ENABLE_EMAIL_NOTIFICATIONS': 'false',
|
||||||
|
'ENABLE_TELEGRAM_NOTIFICATIONS': 'false'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
assert booker.notifier is not None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
367
test/test_crossfit_booker_sessions.py
Normal file
367
test/test_crossfit_booker_sessions.py
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Unit tests for CrossFitBooker session-related methods
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch, Mock, AsyncMock
|
||||||
|
from datetime import datetime, date, timedelta
|
||||||
|
import pytz
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Add the parent directory to the path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from crossfit_booker import CrossFitBooker
|
||||||
|
|
||||||
|
|
||||||
|
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_401_error(self, mock_post):
|
||||||
|
"""Test get_available_sessions with 401 error"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.status_code = 401
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrossFitBookerBookSession:
|
||||||
|
"""Test cases for book_session method"""
|
||||||
|
|
||||||
|
def test_book_session_no_auth(self):
|
||||||
|
"""Test book_session without authentication"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
result = booker.book_session("session_123")
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
@patch('requests.Session.post')
|
||||||
|
def test_book_session_success(self, mock_post):
|
||||||
|
"""Test successful book_session"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response.json.return_value = {"success": True}
|
||||||
|
|
||||||
|
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.book_session("session_123")
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
@patch('requests.Session.post')
|
||||||
|
def test_book_session_api_failure(self, mock_post):
|
||||||
|
"""Test book_session with API failure"""
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response.json.return_value = {"success": False, "error": "Session full"}
|
||||||
|
|
||||||
|
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.book_session("session_123")
|
||||||
|
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrossFitBookerIsSessionBookable:
|
||||||
|
"""Test cases for is_session_bookable method"""
|
||||||
|
|
||||||
|
def test_is_session_bookable_can_join_true(self):
|
||||||
|
"""Test session bookable with can_join=True"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {"user_info": {"can_join": True}}
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
result = booker.is_session_bookable(session, current_time)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_is_session_bookable_booking_window_past(self):
|
||||||
|
"""Test session bookable with booking window in past"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {
|
||||||
|
"user_info": {
|
||||||
|
"can_join": False,
|
||||||
|
"unableToBookUntilDate": "01-01-2020",
|
||||||
|
"unableToBookUntilTime": "10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
result = booker.is_session_bookable(session, current_time)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_is_session_bookable_booking_window_future(self):
|
||||||
|
"""Test session not bookable with booking window in future"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {
|
||||||
|
"user_info": {
|
||||||
|
"can_join": False,
|
||||||
|
"unableToBookUntilDate": "01-01-2030",
|
||||||
|
"unableToBookUntilTime": "10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_time = datetime.now(pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
result = booker.is_session_bookable(session, current_time)
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrossFitBookerRunBookingCycle:
|
||||||
|
"""Test cases for run_booking_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"""
|
||||||
|
mock_get_sessions.return_value = {"success": False}
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
await booker.run_booking_cycle(datetime.now(pytz.timezone("Europe/Paris")))
|
||||||
|
mock_get_sessions.assert_called_once()
|
||||||
|
mock_book_session.assert_not_called()
|
||||||
|
|
||||||
|
@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_with_sessions(self, mock_book_session, mock_matches_preferred, mock_is_bookable, mock_get_sessions):
|
||||||
|
"""Test run_booking_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()
|
||||||
|
|
||||||
|
mock_get_sessions.return_value = {
|
||||||
|
"success": True,
|
||||||
|
"data": {
|
||||||
|
"activities_calendar": [
|
||||||
|
{
|
||||||
|
"id_activity_calendar": "1",
|
||||||
|
"name_activity": "CONDITIONING",
|
||||||
|
"start_timestamp": session_date.strftime("%Y-%m-%d") + " 18:30:00",
|
||||||
|
"user_info": {"can_join": True}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock_is_bookable.return_value = True
|
||||||
|
mock_matches_preferred.return_value = True
|
||||||
|
mock_book_session.return_value = True
|
||||||
|
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
await booker.run_booking_cycle(current_time)
|
||||||
|
|
||||||
|
mock_get_sessions.assert_called_once()
|
||||||
|
mock_is_bookable.assert_called_once()
|
||||||
|
mock_matches_preferred.assert_called_once()
|
||||||
|
mock_book_session.assert_called_once()
|
||||||
|
assert mock_book_session.call_count == 1
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""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()
|
||||||
|
|
||||||
|
@patch('crossfit_booker.CrossFitBooker.login')
|
||||||
|
@patch('crossfit_booker.CrossFitBooker.run_booking_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):
|
||||||
|
"""Test run with booking outside window"""
|
||||||
|
mock_login.return_value = True
|
||||||
|
mock_quit.return_value = None # Prevent actual exit
|
||||||
|
|
||||||
|
# Create a time outside the booking window (19:00)
|
||||||
|
tz = pytz.timezone("Europe/Paris")
|
||||||
|
mock_now = datetime(2025, 7, 25, 19, 0, tzinfo=tz)
|
||||||
|
mock_datetime.now.return_value = mock_now
|
||||||
|
|
||||||
|
# Make sleep return immediately to allow one iteration, then break
|
||||||
|
call_count = 0
|
||||||
|
def sleep_side_effect(seconds):
|
||||||
|
nonlocal call_count
|
||||||
|
call_count += 1
|
||||||
|
if call_count >= 1:
|
||||||
|
# Break the loop after first sleep
|
||||||
|
raise KeyboardInterrupt("Test complete")
|
||||||
|
return None
|
||||||
|
|
||||||
|
mock_sleep.side_effect = sleep_side_effect
|
||||||
|
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await booker.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass # Expected to break the loop
|
||||||
|
|
||||||
|
# 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 quit was called (due to KeyboardInterrupt)
|
||||||
|
mock_quit.assert_called_once()
|
||||||
|
|
||||||
|
class TestCrossFitBookerQuit:
|
||||||
|
"""Test cases for quit method"""
|
||||||
|
|
||||||
|
def test_quit(self):
|
||||||
|
"""Test quit method"""
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
with patch('sys.exit') as mock_exit:
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
booker.quit()
|
||||||
|
assert excinfo.value.code == 0
|
||||||
|
class TestCrossFitBookerMatchesPreferredSession:
|
||||||
|
"""Test cases for matches_preferred_session method"""
|
||||||
|
|
||||||
|
def test_matches_preferred_session_exact_match(self):
|
||||||
|
"""Test exact match with preferred session"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00",
|
||||||
|
"name_activity": "CONDITIONING"
|
||||||
|
}
|
||||||
|
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
# Mock PREFERRED_SESSIONS
|
||||||
|
with patch('crossfit_booker.PREFERRED_SESSIONS', [(2, "18:30", "CONDITIONING")]):
|
||||||
|
result = booker.matches_preferred_session(session, current_time)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_matches_preferred_session_fuzzy_match(self):
|
||||||
|
"""Test fuzzy match with preferred session"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00",
|
||||||
|
"name_activity": "CONDITIONING WORKOUT"
|
||||||
|
}
|
||||||
|
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
# Mock PREFERRED_SESSIONS
|
||||||
|
with patch('crossfit_booker.PREFERRED_SESSIONS', [(2, "18:30", "CONDITIONING")]):
|
||||||
|
result = booker.matches_preferred_session(session, current_time)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_matches_preferred_session_no_match(self):
|
||||||
|
"""Test no match with preferred session"""
|
||||||
|
with patch.dict(os.environ, {
|
||||||
|
'CROSSFIT_USERNAME': 'test_user',
|
||||||
|
'CROSSFIT_PASSWORD': 'test_pass'
|
||||||
|
}):
|
||||||
|
booker = CrossFitBooker()
|
||||||
|
session = {
|
||||||
|
"start_timestamp": "2025-07-30 18:30:00",
|
||||||
|
"name_activity": "YOGA"
|
||||||
|
}
|
||||||
|
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
|
||||||
|
|
||||||
|
# Mock PREFERRED_SESSIONS
|
||||||
|
with patch('crossfit_booker.PREFERRED_SESSIONS', [(2, "18:30", "CONDITIONING")]):
|
||||||
|
result = booker.matches_preferred_session(session, current_time)
|
||||||
|
assert result is False
|
||||||
Reference in New Issue
Block a user