From f89d984623fc9377a5032bb81f30a98685041489 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Wed, 16 Jul 2025 09:04:05 +0200 Subject: [PATCH] Refactor tests: break down single test into focused, modular tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add pytest fixtures for shared data to avoid reparsing YAML files - Split large test into specific test functions for better modularity - Add comprehensive test coverage for busy_event, location tracking, and helper functions - Improve test performance with session-scoped fixtures - Enable individual test execution with pytest -k 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tests/test_busy.py | 243 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 199 insertions(+), 44 deletions(-) diff --git a/tests/test_busy.py b/tests/test_busy.py index 991045e..150dc8f 100644 --- a/tests/test_busy.py +++ b/tests/test_busy.py @@ -1,68 +1,223 @@ -from datetime import date, datetime, timedelta +from datetime import date, datetime import agenda.busy import agenda.travel as travel import agenda.trip +import pytest +from agenda.busy import _parse_datetime_field +from agenda.event import Event from web_view import app -def test_get_location_for_date() -> None: +@pytest.fixture(scope="session") +def app_context(): + """Set up Flask app context for tests.""" app.config["SERVER_NAME"] = "test" with app.app_context(): - today = datetime.now().date() - trips = agenda.trip.build_trip_list() + yield - data_dir = app.config["PERSONAL_DATA"] - # Parse YAML files once for the test - bookings = travel.parse_yaml("flights", data_dir) - accommodations = travel.parse_yaml("accommodation", data_dir) - airports = travel.parse_yaml("airports", data_dir) +@pytest.fixture(scope="session") +def trips(app_context): + """Load trip list once for all tests.""" + return agenda.trip.build_trip_list() - for year in range(2023, 2025): - start = date(2023, 1, 1) - busy_events = agenda.busy.get_busy_events(start, app.config, trips) - weekends = agenda.busy.weekends(start, busy_events, trips, data_dir) - for weekend in weekends: - for day in "saturday", "sunday": - # When free (no events), should be home (None) - # When traveling (events), should be away (City name) - assert bool(weekend[day + "_location"][0]) == bool(weekend[day]) +@pytest.fixture(scope="session") +def travel_data(app_context): + """Load travel data (bookings, accommodations, airports) once for all tests.""" + data_dir = app.config["PERSONAL_DATA"] + return { + "bookings": travel.parse_yaml("flights", data_dir), + "accommodations": travel.parse_yaml("accommodation", data_dir), + "airports": travel.parse_yaml("airports", data_dir), + "data_dir": data_dir, + } - # Test some specific cases - april_29_location = agenda.busy.get_location_for_date( - date(2023, 4, 29), trips, bookings, accommodations, airports + +def test_weekend_location_consistency(app_context, trips, travel_data): + """Test that weekend locations are consistent with events (free=home, events=away).""" + for year in range(2023, 2025): + start = date(2023, 1, 1) + busy_events = agenda.busy.get_busy_events(start, app.config, trips) + weekends = agenda.busy.weekends( + start, busy_events, trips, travel_data["data_dir"] ) - assert not april_29_location[0] # Should be home (None) - l = agenda.busy.get_location_for_date( - date(2025, 2, 15), trips, bookings, accommodations, airports + for weekend in weekends: + for day in "saturday", "sunday": + # When free (no events), should be home (None) + # When traveling (events), should be away (City name) + location_exists = bool(weekend[day + "_location"][0]) + has_events = bool(weekend[day]) + assert location_exists == has_events, ( + f"Weekend {weekend['date']} {day}: " + f"location_exists={location_exists}, has_events={has_events}" + ) + + +def test_specific_home_dates(travel_data): + """Test specific dates that should return home (None).""" + trips = agenda.trip.build_trip_list() + + home_dates = [ + date(2023, 4, 29), + date(2025, 7, 1), + date(2023, 12, 2), + date(2023, 10, 7), + date(2023, 2, 18), + date(2025, 8, 2), + ] + + for test_date in home_dates: + location = agenda.busy.get_location_for_date( + test_date, + trips, + travel_data["bookings"], + travel_data["accommodations"], + travel_data["airports"], ) - assert l[0] == "Hackettstown" + assert not location[ + 0 + ], f"Expected home (None) for {test_date}, got {location[0]}" - l = agenda.busy.get_location_for_date( - date(2025, 7, 1), trips, bookings, accommodations, airports + +def test_specific_away_dates(travel_data): + """Test specific dates that should return away locations.""" + trips = agenda.trip.build_trip_list() + + away_cases = [ + (date(2025, 2, 15), "Hackettstown"), + ] + + for test_date, expected_city in away_cases: + location = agenda.busy.get_location_for_date( + test_date, + trips, + travel_data["bookings"], + travel_data["accommodations"], + travel_data["airports"], ) - assert not l[0] + assert ( + location[0] == expected_city + ), f"Expected {expected_city} for {test_date}, got {location[0]}" - l = agenda.busy.get_location_for_date( - date(2023, 12, 2), trips, bookings, accommodations, airports - ) - assert not l[0] - l = agenda.busy.get_location_for_date( - date(2023, 10, 7), trips, bookings, accommodations, airports - ) - assert not l[0] +def test_get_location_for_date_basic(travel_data): + """Test basic functionality of get_location_for_date function.""" + trips = agenda.trip.build_trip_list() + test_date = date(2023, 1, 1) - l = agenda.busy.get_location_for_date( - date(2023, 2, 18), trips, bookings, accommodations, airports - ) - assert not l[0] + location = agenda.busy.get_location_for_date( + test_date, + trips, + travel_data["bookings"], + travel_data["accommodations"], + travel_data["airports"], + ) - l = agenda.busy.get_location_for_date( - date(2025, 8, 2), trips, bookings, accommodations, airports - ) - assert not l[0] + # Should return a tuple with (city|None, country) + assert isinstance(location, tuple) + assert len(location) == 2 + assert location[1] is not None # Should always have a country + +def test_busy_event_classification(): + """Test the busy_event function for different event types.""" + + # Busy event types + busy_events = [ + Event(name="event", title="Test Event", date=date(2023, 1, 1)), + Event( + name="conference", + title="Test Conference", + date=date(2023, 1, 1), + going=True, + ), + Event(name="accommodation", title="Hotel", date=date(2023, 1, 1)), + Event(name="transport", title="Flight", date=date(2023, 1, 1)), + ] + + for event in busy_events: + assert agenda.busy.busy_event(event), f"Event {event.name} should be busy" + + # Non-busy events + non_busy_events = [ + Event( + name="conference", + title="Test Conference", + date=date(2023, 1, 1), + going=False, + ), + Event(name="other", title="Other Event", date=date(2023, 1, 1)), + Event(name="event", title="LHG Run Club", date=date(2023, 1, 1)), + Event(name="event", title="IA UK board meeting", date=date(2023, 1, 1)), + ] + + for event in non_busy_events: + assert not agenda.busy.busy_event( + event + ), f"Event {event.name}/{event.title} should not be busy" + + +def test_parse_datetime_field(): + """Test the _parse_datetime_field helper function.""" + + # Test with datetime object + dt = datetime(2023, 1, 1, 12, 0, 0) + parsed_dt, parsed_date = _parse_datetime_field(dt) + assert parsed_dt == dt + assert parsed_date == date(2023, 1, 1) + + # Test with ISO string + iso_string = "2023-01-01T12:00:00Z" + parsed_dt, parsed_date = _parse_datetime_field(iso_string) + assert parsed_date == date(2023, 1, 1) + assert parsed_dt.year == 2023 + assert parsed_dt.month == 1 + assert parsed_dt.day == 1 + + +def test_get_busy_events(app_context, trips): + """Test get_busy_events function.""" + start_date = date(2023, 1, 1) + busy_events = agenda.busy.get_busy_events(start_date, app.config, trips) + + # Should return a list + assert isinstance(busy_events, list) + + # All events should be Event objects + for event in busy_events: + assert hasattr(event, "name") + assert hasattr(event, "as_date") + + # Events should be sorted by date + dates = [event.as_date for event in busy_events] + assert dates == sorted(dates), "Events should be sorted by date" + + +def test_weekends_function(app_context, trips, travel_data): + """Test the weekends function.""" + start_date = date(2023, 1, 1) + busy_events = agenda.busy.get_busy_events(start_date, app.config, trips) + weekends = agenda.busy.weekends( + start_date, busy_events, trips, travel_data["data_dir"] + ) + + # Should return a list of weekend info + assert isinstance(weekends, list) + assert len(weekends) == 52 # Should return 52 weekends + + # Each weekend should have the required keys + for weekend in weekends[:5]: # Check first 5 weekends + assert "date" in weekend + assert "saturday" in weekend + assert "sunday" in weekend + assert "saturday_location" in weekend + assert "sunday_location" in weekend + + # Locations should be tuples + assert isinstance(weekend["saturday_location"], tuple) + assert isinstance(weekend["sunday_location"], tuple) + assert len(weekend["saturday_location"]) == 2 + assert len(weekend["sunday_location"]) == 2