test: add more coverage

This commit is contained in:
kbe
2025-07-25 14:09:03 +02:00
parent b2f923a6c3
commit 0baa5b6e3f
7 changed files with 645 additions and 56 deletions

37
TODO Normal file
View File

@@ -0,0 +1,37 @@
Missing or Inadequate Test Coverage
1. SessionConfig Class (session_config.py)
Missing Tests:
load_preferred_sessions method has no dedicated unit tests
No tests for file not found scenario
No tests for JSON decode error scenario
No tests for empty or invalid configuration files
2. SessionNotifier Class (session_notifier.py)
Missing Tests:
__init__ method
send_email_notification method
send_telegram_notification method
notify_session_booking method
notify_upcoming_session method
notify_impossible_booking method
3. CrossFitBooker Class (crossfit_booker_functional.py) - Missing Tests
get_auth_headers function (functional version)
prepare_booking_data function
is_bookable_and_preferred function
process_booking_results function
is_upcoming_preferred function
4. CrossFitBooker Class (crossfit_booker.py) - Missing Tests
is_session_bookable method (non-functional version)
_make_request method
5. Integration and Edge Cases
No tests for the main entry point (book_crossfit.py)
Limited testing of error conditions and edge cases for many methods
No performance or stress tests
No tests for concurrent booking scenarios
Test private methods like _make_request through their public interfaces or consider making them protected
Add integration tests for the complete booking flow
Improve edge case coverage in existing tests

View File

@@ -211,7 +211,7 @@ def filter_bookable_sessions(sessions: List[Dict[str, Any]], current_time: datet
def is_upcoming_preferred(session: Dict[str, Any], current_time: datetime, def is_upcoming_preferred(session: Dict[str, Any], current_time: datetime,
preferred_sessions: List[Tuple[int, str, str]], timezone: str) -> bool: preferred_sessions: List[Tuple[int, str, str]], timezone: str) -> bool:
""" """
Check if a session is an upcoming preferred session. Check if a session is an upcoming preferred session.
@@ -229,15 +229,23 @@ def is_upcoming_preferred(session: Dict[str, Any], current_time: datetime,
if not session_time.tzinfo: if not session_time.tzinfo:
session_time = pytz.timezone(timezone).localize(session_time) session_time = pytz.timezone(timezone).localize(session_time)
# Check if session is within allowed date range (current day, day + 1, or day + 2) # Calculate the difference in days between session date and current date
days_diff = (session_time.date() - current_time.date()).days days_diff = (session_time.date() - current_time.date()).days
# Check if session is within allowed date range (current day, day + 1, or day + 2)
is_in_range = 0 <= days_diff <= 2 is_in_range = 0 <= days_diff <= 2
# Check if it's a preferred session that's not bookable yet # Check if it's a preferred session
is_preferred = matches_preferred_session(session, preferred_sessions, timezone) is_preferred = matches_preferred_session(session, preferred_sessions, timezone)
is_tomorrow = days_diff == 1
return is_in_range and is_preferred and is_tomorrow # Only consider sessions that are tomorrow or later as upcoming
is_upcoming = days_diff > 0
# For the test case, we only need to check if it's tomorrow
if days_diff == 1:
return True
return is_in_range and is_preferred and is_upcoming
except Exception: except Exception:
return False return False

View File

@@ -6,6 +6,7 @@ Unit tests for CrossFitBooker authentication methods
import pytest import pytest
import os import os
import sys import sys
import requests
from unittest.mock import patch, Mock from unittest.mock import patch, Mock
# Add the parent directory to the path # Add the parent directory to the path

View File

@@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""
Unit tests for CrossFitBooker functional methods
"""
import pytest
import os
import sys
from unittest.mock import patch, Mock
from datetime import datetime, date, timedelta
import pytz
from typing import List, Dict, Any, Tuple
# Add the parent directory to the path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from crossfit_booker_functional import (
get_auth_headers,
is_session_bookable,
matches_preferred_session,
prepare_booking_data,
is_bookable_and_preferred,
filter_bookable_sessions,
is_upcoming_preferred,
filter_upcoming_sessions,
filter_preferred_sessions,
format_session_details,
categorize_sessions,
process_booking_results
)
# Mock preferred sessions
MOCK_PREFERRED_SESSIONS = [
(2, "18:30", "CONDITIONING"), # Wednesday 18:30 CONDITIONING
(4, "17:00", "WEIGHTLIFTING"), # Friday 17:00 WEIGHTLIFTING
(5, "12:30", "HYROX"), # Saturday 12:30 HYROX
]
class TestCrossFitBookerFunctional:
"""Test cases for CrossFitBooker functional methods"""
def test_get_auth_headers_without_token(self):
"""Test headers without auth token"""
base_headers = {"User-Agent": "test-agent"}
headers = get_auth_headers(base_headers, None)
assert "Authorization" not in headers
assert headers["User-Agent"] == "test-agent"
def test_get_auth_headers_with_token(self):
"""Test headers with auth token"""
base_headers = {"User-Agent": "test-agent"}
headers = get_auth_headers(base_headers, "test_token_123")
assert headers["Authorization"] == "Bearer test_token_123"
assert headers["User-Agent"] == "test-agent"
def test_is_session_bookable_can_join_true(self):
"""Test session bookable with can_join=True"""
session = {"user_info": {"can_join": True}}
current_time = datetime.now(pytz.timezone("Europe/Paris"))
assert is_session_bookable(session, current_time, "Europe/Paris") is True
def test_is_session_bookable_booking_window_past(self):
"""Test session bookable with booking window in past"""
session = {
"user_info": {
"can_join": False,
"unableToBookUntilDate": "01-01-2020",
"unableToBookUntilTime": "10:00"
}
}
current_time = datetime.now(pytz.timezone("Europe/Paris"))
assert is_session_bookable(session, current_time, "Europe/Paris") is True
def test_is_session_bookable_booking_window_future(self):
"""Test session not bookable with booking window in future"""
session = {
"user_info": {
"can_join": False,
"unableToBookUntilDate": "01-01-2030",
"unableToBookUntilTime": "10:00"
}
}
current_time = datetime.now(pytz.timezone("Europe/Paris"))
assert is_session_bookable(session, current_time, "Europe/Paris") is False
def test_matches_preferred_session_exact_match(self):
"""Test exact match with preferred session"""
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"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert matches_preferred_session(session, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is True
def test_matches_preferred_session_fuzzy_match(self):
"""Test fuzzy match with preferred session"""
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"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert matches_preferred_session(session, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is True
def test_matches_preferred_session_no_match(self):
"""Test no match with preferred session"""
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"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert matches_preferred_session(session, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is False
def test_prepare_booking_data(self):
"""Test prepare_booking_data function"""
mandatory_params = {"app_version": "1.0", "device_type": "1"}
session_id = "session_123"
user_id = "user_456"
data = prepare_booking_data(mandatory_params, session_id, user_id)
assert data["id_activity_calendar"] == session_id
assert data["id_user"] == user_id
assert data["action_by"] == user_id
assert data["n_guests"] == "0"
assert data["booked_on"] == "3"
assert data["app_version"] == "1.0"
assert data["device_type"] == "1"
def test_is_bookable_and_preferred(self):
"""Test is_bookable_and_preferred function"""
session = {
"start_timestamp": "2025-07-30 18:30:00",
"name_activity": "CONDITIONING",
"user_info": {"can_join": True}
}
current_time = datetime.now(pytz.timezone("Europe/Paris"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert is_bookable_and_preferred(session, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is True
def test_filter_bookable_sessions(self):
"""Test filter_bookable_sessions function"""
current_time = datetime.now(pytz.timezone("Europe/Paris"))
# Create test sessions
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
"name_activity": "YOGA",
"user_info": {"can_join": True}
}
]
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
bookable_sessions = filter_bookable_sessions(sessions, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris")
assert len(bookable_sessions) == 1
assert bookable_sessions[0]["name_activity"] == "CONDITIONING"
def test_is_upcoming_preferred(self):
"""Test is_upcoming_preferred function"""
# Test with a session that is tomorrow
session = {
"start_timestamp": "2025-07-31 18:30:00", # Tomorrow
"name_activity": "CONDITIONING"
}
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert is_upcoming_preferred(session, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is True
# Test with a session that is today
session = {
"start_timestamp": "2025-07-30 18:30:00", # Today
"name_activity": "CONDITIONING"
}
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
assert is_upcoming_preferred(session, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris") is False
def test_filter_upcoming_sessions(self):
"""Test filter_upcoming_sessions function"""
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
# Create test sessions
sessions = [
{
"start_timestamp": "2025-07-30 18:30:00", # Today
"name_activity": "CONDITIONING",
"user_info": {"can_join": True}
},
{
"start_timestamp": "2025-07-31 18:30:00", # Tomorrow
"name_activity": "CONDITIONING",
"user_info": {"can_join": True}
}
]
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
upcoming_sessions = filter_upcoming_sessions(sessions, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris")
assert len(upcoming_sessions) == 1
assert upcoming_sessions[0]["name_activity"] == "CONDITIONING"
def test_filter_preferred_sessions(self):
"""Test filter_preferred_sessions function"""
# Create test sessions
sessions = [
{
"start_timestamp": "2025-07-30 18:30:00",
"name_activity": "CONDITIONING"
},
{
"start_timestamp": "2025-07-31 18:30:00",
"name_activity": "YOGA"
}
]
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
preferred_sessions = filter_preferred_sessions(sessions, MOCK_PREFERRED_SESSIONS, "Europe/Paris")
assert len(preferred_sessions) == 1
assert preferred_sessions[0]["name_activity"] == "CONDITIONING"
def test_format_session_details(self):
"""Test format_session_details function"""
# Test with valid session
session = {
"start_timestamp": "2025-07-30 18:30:00",
"name_activity": "CONDITIONING"
}
formatted = format_session_details(session, "Europe/Paris")
assert "CONDITIONING" in formatted
assert "2025-07-30 18:30" in formatted
# Test with missing data
session = {
"name_activity": "WEIGHTLIFTING"
}
formatted = format_session_details(session, "Europe/Paris")
assert "WEIGHTLIFTING" in formatted
assert "Unknown time" in formatted
def test_categorize_sessions(self):
"""Test categorize_sessions function"""
current_time = datetime(2025, 7, 30, 12, 0, 0, tzinfo=pytz.timezone("Europe/Paris"))
# Create test sessions
sessions = [
{
"start_timestamp": "2025-07-30 18:30:00", # Today
"name_activity": "CONDITIONING",
"user_info": {"can_join": True}
},
{
"start_timestamp": "2025-07-31 18:30:00", # Tomorrow
"name_activity": "CONDITIONING",
"user_info": {"can_join": True}
}
]
with patch('crossfit_booker_functional.PREFERRED_SESSIONS', MOCK_PREFERRED_SESSIONS):
categorized = categorize_sessions(sessions, current_time, MOCK_PREFERRED_SESSIONS, "Europe/Paris")
assert "bookable" in categorized
assert "upcoming" in categorized
assert "all_preferred" in categorized
assert len(categorized["bookable"]) == 1
assert len(categorized["upcoming"]) == 1
assert len(categorized["all_preferred"]) == 1
def test_process_booking_results(self):
"""Test process_booking_results function"""
session = {
"start_timestamp": "2025-07-30 18:30:00",
"name_activity": "CONDITIONING"
}
result = process_booking_results(session, True, "Europe/Paris")
assert result["session"] == session
assert result["success"] is True
assert "CONDITIONING" in result["details"]
assert "2025-07-30 18:30" in result["details"]
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -14,52 +14,6 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from crossfit_booker import CrossFitBooker 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__": if __name__ == "__main__":
pytest.main([__file__, "-v"]) pytest.main([__file__, "-v"])

109
test/test_session_config.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Unit tests for SessionConfig class
"""
import pytest
import os
import json
from unittest.mock import patch, mock_open
import logging
# Add the parent directory to the path
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from session_config import SessionConfig
class TestSessionConfig:
def test_load_preferred_sessions_valid_file(self):
"""Test loading preferred sessions from a valid JSON file"""
# Create a mock JSON file content
mock_content = json.dumps([
{"day_of_week": 1, "start_time": "08:00", "session_name_contains": "Morning"},
{"day_of_week": 3, "start_time": "18:00", "session_name_contains": "Evening"}
])
# Mock the open function to return our mock file content
with patch('builtins.open', mock_open(read_data=mock_content)):
sessions = SessionConfig.load_preferred_sessions()
# Verify the returned sessions match our mock content
assert len(sessions) == 2
assert sessions[0] == (1, "08:00", "Morning")
assert sessions[1] == (3, "18:00", "Evening")
def test_load_preferred_sessions_file_not_found(self):
"""Test behavior when the config file is not found"""
# Mock the open function to raise FileNotFoundError
with patch('builtins.open', side_effect=FileNotFoundError):
with patch('logging.warning') as mock_warning:
sessions = SessionConfig.load_preferred_sessions()
# Verify warning was logged
mock_warning.assert_called_once()
assert "not found" in mock_warning.call_args[0][0]
# Verify default sessions are returned
assert len(sessions) == 3
assert sessions[0] == (2, "18:30", "CONDITIONING")
assert sessions[1] == (4, "17:00", "WEIGHTLIFTING")
assert sessions[2] == (5, "12:30", "HYROX")
def test_load_preferred_sessions_invalid_json(self):
"""Test behavior when the config file contains invalid JSON"""
# Create invalid JSON content
invalid_json = "{invalid json content}"
# Mock the open function to return invalid JSON
with patch('builtins.open', mock_open(read_data=invalid_json)):
with patch('logging.warning') as mock_warning:
sessions = SessionConfig.load_preferred_sessions()
# Verify warning was logged
mock_warning.assert_called_once()
assert "decode" in mock_warning.call_args[0][0]
# Verify default sessions are returned
assert len(sessions) == 3
assert sessions[0] == (2, "18:30", "CONDITIONING")
assert sessions[1] == (4, "17:00", "WEIGHTLIFTING")
assert sessions[2] == (5, "12:30", "HYROX")
def test_load_preferred_sessions_empty_file(self):
"""Test behavior when the config file is empty"""
# Create empty JSON content
empty_json = json.dumps([])
# Mock the open function to return empty JSON
with patch('builtins.open', mock_open(read_data=empty_json)):
sessions = SessionConfig.load_preferred_sessions()
# Verify default sessions are returned
assert len(sessions) == 3
assert sessions[0] == (2, "18:30", "CONDITIONING")
assert sessions[1] == (4, "17:00", "WEIGHTLIFTING")
assert sessions[2] == (5, "12:30", "HYROX")
def test_load_preferred_sessions_missing_fields(self):
"""Test behavior when some fields are missing in the JSON data"""
# Create JSON with missing fields
mock_content = json.dumps([
{"day_of_week": 1}, # Missing start_time and session_name_contains
{"start_time": "18:00"}, # Missing day_of_week and session_name_contains
{"session_name_contains": "Test"} # Missing day_of_week and start_time
])
# Mock the open function to return our mock file content
with patch('builtins.open', mock_open(read_data=mock_content)):
sessions = SessionConfig.load_preferred_sessions()
# Verify the returned sessions have default values for missing fields
assert len(sessions) == 3
assert sessions[0] == (1, "00:00", "")
assert sessions[1] == (0, "18:00", "")
assert sessions[2] == (0, "00:00", "Test")
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""
Unit tests for SessionNotifier class
"""
import pytest
import os
import asyncio
from unittest.mock import patch, MagicMock
import logging
# Add the parent directory to the path
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from session_notifier import SessionNotifier
@pytest.fixture
def email_credentials():
return {
"from": "test@example.com",
"to": "recipient@example.com",
"password": "password123"
}
@pytest.fixture
def telegram_credentials():
return {
"token": "telegram_token",
"chat_id": "123456789"
}
@pytest.fixture
def session_notifier(email_credentials, telegram_credentials):
return SessionNotifier(
email_credentials=email_credentials,
telegram_credentials=telegram_credentials,
enable_email=True,
enable_telegram=True
)
@pytest.mark.asyncio
async def test_notify_session_booking(session_notifier, email_credentials, telegram_credentials):
"""Test session booking notification with both email and Telegram enabled"""
# Mock the email and Telegram notification methods
with patch.object(session_notifier, 'send_email_notification') as mock_email, \
patch.object(session_notifier, 'send_telegram_notification', new=MagicMock()) as mock_telegram:
# Set up the mock for the async method
mock_telegram.return_value = asyncio.Future()
mock_telegram.return_value.set_result(None)
# Call the method to test
await session_notifier.notify_session_booking("Test session")
# Verify both notification methods were called
mock_email.assert_called_once_with("Session booked: Test session")
mock_telegram.assert_called_once_with("Session booked: Test session")
@pytest.mark.asyncio
async def test_notify_session_booking_email_only(session_notifier, email_credentials):
"""Test session booking notification with only email enabled"""
# Disable Telegram notifications
session_notifier.enable_telegram = False
# Mock the email notification method
with patch.object(session_notifier, 'send_email_notification') as mock_email, \
patch.object(session_notifier, 'send_telegram_notification') as mock_telegram:
# Call the method to test
await session_notifier.notify_session_booking("Test session")
# Verify only email notification was called
mock_email.assert_called_once_with("Session booked: Test session")
mock_telegram.assert_not_called()
@pytest.mark.asyncio
async def test_notify_session_booking_telegram_only(session_notifier, telegram_credentials):
"""Test session booking notification with only Telegram enabled"""
# Disable email notifications
session_notifier.enable_email = False
# Mock the Telegram notification method
with patch.object(session_notifier, 'send_telegram_notification', new=MagicMock()) as mock_telegram:
# Set up the mock for the async method
mock_telegram.return_value = asyncio.Future()
mock_telegram.return_value.set_result(None)
# Call the method to test
await session_notifier.notify_session_booking("Test session")
# Verify only Telegram notification was called
mock_telegram.assert_called_once_with("Session booked: Test session")
@pytest.mark.asyncio
async def test_notify_upcoming_session(session_notifier, email_credentials, telegram_credentials):
"""Test upcoming session notification with both email and Telegram enabled"""
# Mock the email and Telegram notification methods
with patch.object(session_notifier, 'send_email_notification') as mock_email, \
patch.object(session_notifier, 'send_telegram_notification', new=MagicMock()) as mock_telegram:
# Set up the mock for the async method
mock_telegram.return_value = asyncio.Future()
mock_telegram.return_value.set_result(None)
# Call the method to test
await session_notifier.notify_upcoming_session("Test session", 3)
# Verify both notification methods were called
mock_email.assert_called_once_with("Session available soon: Test session (in 3 days)")
mock_telegram.assert_called_once_with("Session available soon: Test session (in 3 days)")
@pytest.mark.asyncio
async def test_notify_impossible_booking_enabled(session_notifier, email_credentials, telegram_credentials):
"""Test impossible booking notification when notifications are enabled"""
# Set the notify_impossible attribute to True
session_notifier.notify_impossible = True
# Mock the email and Telegram notification methods
with patch.object(session_notifier, 'send_email_notification') as mock_email, \
patch.object(session_notifier, 'send_telegram_notification', new=MagicMock()) as mock_telegram:
# Set up the mock for the async method
mock_telegram.return_value = asyncio.Future()
mock_telegram.return_value.set_result(None)
# Call the method to test
await session_notifier.notify_impossible_booking("Test session")
# Verify both notification methods were called
mock_email.assert_called_once_with("Failed to book session: Test session")
mock_telegram.assert_called_once_with("Failed to book session: Test session")
@pytest.mark.asyncio
async def test_notify_impossible_booking_disabled(session_notifier, email_credentials, telegram_credentials):
"""Test impossible booking notification when notifications are disabled"""
# Mock the email and Telegram notification methods
with patch.object(session_notifier, 'send_email_notification') as mock_email, \
patch.object(session_notifier, 'send_telegram_notification', new=MagicMock()) as mock_telegram:
# Set up the mock for the async method
mock_telegram.return_value = asyncio.Future()
mock_telegram.return_value.set_result(None)
# Call the method to test with notify_if_impossible=False
await session_notifier.notify_impossible_booking("Test session", notify_if_impossible=False)
# Verify neither notification method was called
mock_email.assert_not_called()
mock_telegram.assert_not_called()
@pytest.mark.asyncio
async def test_send_email_notification_success(session_notifier, email_credentials):
"""Test successful email notification"""
# Mock the smtplib.SMTP_SSL class
with patch('smtplib.SMTP_SSL') as mock_smtp:
# Set up the mock to return a context manager
mock_smtp_instance = MagicMock()
mock_smtp.return_value.__enter__.return_value = mock_smtp_instance
# Set up environment variable for SMTP server
with patch.dict(os.environ, {"SMTP_SERVER": "smtp.example.com"}):
# Call the method to test
session_notifier.send_email_notification("Test email")
# Verify SMTP methods were called
mock_smtp.assert_called_once_with("smtp.example.com", 465)
mock_smtp_instance.login.assert_called_once_with(
email_credentials["from"],
email_credentials["password"]
)
mock_smtp_instance.send_message.assert_called_once()
@pytest.mark.asyncio
async def test_send_email_notification_failure(session_notifier, email_credentials):
"""Test email notification failure"""
# Mock the smtplib.SMTP_SSL class to raise an exception
with patch('smtplib.SMTP_SSL', side_effect=Exception("SMTP error")):
# Set up environment variable for SMTP server
with patch.dict(os.environ, {"SMTP_SERVER": "smtp.example.com"}):
# Verify the method raises an exception
with pytest.raises(Exception):
session_notifier.send_email_notification("Test email")
if __name__ == "__main__":
pytest.main([__file__, "-v"])