diff --git a/tests/test_conference.py b/tests/test_conference.py new file mode 100644 index 0000000..811bdb9 --- /dev/null +++ b/tests/test_conference.py @@ -0,0 +1,370 @@ +"""Tests for agenda.conference module.""" + +import decimal +import tempfile +from datetime import date, datetime +from typing import Any + +import pytest +import yaml + +from agenda.conference import Conference, get_list +from agenda.event import Event + + +class TestConference: + """Tests for Conference dataclass.""" + + def test_conference_creation_minimal(self) -> None: + """Test creating conference with minimal required fields.""" + conf = Conference( + name="PyCon", + topic="Python", + location="Portland", + start=date(2024, 5, 15), + end=date(2024, 5, 17), + ) + assert conf.name == "PyCon" + assert conf.topic == "Python" + assert conf.location == "Portland" + assert conf.start == date(2024, 5, 15) + assert conf.end == date(2024, 5, 17) + assert conf.trip is None + assert conf.going is False + assert conf.online is False + + def test_conference_creation_full(self) -> None: + """Test creating conference with all fields.""" + conf = Conference( + name="PyCon US", + topic="Python", + location="Portland", + start=date(2024, 5, 15), + end=date(2024, 5, 17), + trip=date(2024, 5, 14), + country="USA", + venue="Convention Center", + address="123 Main St", + url="https://pycon.org", + accommodation_booked=True, + transport_booked=True, + going=True, + registered=True, + speaking=True, + online=False, + price=decimal.Decimal("500.00"), + currency="USD", + latitude=45.5152, + longitude=-122.6784, + cfp_end=date(2024, 2, 1), + cfp_url="https://pycon.org/cfp", + free=False, + hackathon=True, + ticket_type="early_bird", + attendees=3000, + hashtag="#pycon2024", + ) + assert conf.name == "PyCon US" + assert conf.going is True + assert conf.price == decimal.Decimal("500.00") + assert conf.currency == "USD" + assert conf.latitude == 45.5152 + assert conf.longitude == -122.6784 + assert conf.cfp_end == date(2024, 2, 1) + assert conf.hashtag == "#pycon2024" + + def test_display_name_location_in_name(self) -> None: + """Test display_name when location is already in conference name.""" + conf = Conference( + name="PyCon Portland", + topic="Python", + location="Portland", + start=date(2024, 5, 15), + end=date(2024, 5, 17), + ) + assert conf.display_name == "PyCon Portland" + + def test_display_name_location_not_in_name(self) -> None: + """Test display_name when location is not in conference name.""" + conf = Conference( + name="PyCon", + topic="Python", + location="Portland", + start=date(2024, 5, 15), + end=date(2024, 5, 17), + ) + assert conf.display_name == "PyCon (Portland)" + + def test_display_name_partial_location_match(self) -> None: + """Test display_name when location is partially in name.""" + conf = Conference( + name="PyConf", + topic="Python", + location="Conference Center", + start=date(2024, 5, 15), + end=date(2024, 5, 17), + ) + assert conf.display_name == "PyConf (Conference Center)" + + def test_conference_with_datetime(self) -> None: + """Test conference with datetime objects.""" + start_dt = datetime(2024, 5, 15, 9, 0) + end_dt = datetime(2024, 5, 17, 17, 0) + conf = Conference( + name="PyCon", + topic="Python", + location="Portland", + start=start_dt, + end=end_dt, + ) + assert conf.start == start_dt + assert conf.end == end_dt + + +class TestGetList: + """Tests for get_list function.""" + + def test_get_list_single_conference(self) -> None: + """Test reading single conference from YAML.""" + yaml_data = [ + { + "name": "PyCon", + "topic": "Python", + "location": "Portland", + "start": date(2024, 5, 15), + "end": date(2024, 5, 17), + "url": "https://pycon.org", + "going": True, + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 1 + event = events[0] + assert isinstance(event, Event) + assert event.name == "conference" + assert event.date == date(2024, 5, 15) + assert event.end_date == date(2024, 5, 17) + assert event.title == "PyCon (Portland)" + assert event.url == "https://pycon.org" + assert event.going is True + + def test_get_list_conference_with_cfp(self) -> None: + """Test reading conference with CFP end date.""" + yaml_data = [ + { + "name": "PyCon", + "topic": "Python", + "location": "Portland", + "start": date(2024, 5, 15), + "end": date(2024, 5, 17), + "url": "https://pycon.org", + "cfp_end": date(2024, 2, 1), + "cfp_url": "https://pycon.org/cfp", + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 2 + + # Conference event + conf_event = events[0] + assert conf_event.name == "conference" + assert conf_event.title == "PyCon (Portland)" + assert conf_event.url == "https://pycon.org" + + # CFP end event + cfp_event = events[1] + assert cfp_event.name == "cfp_end" + assert cfp_event.date == date(2024, 2, 1) + assert cfp_event.title == "CFP end: PyCon (Portland)" + assert cfp_event.url == "https://pycon.org/cfp" + + def test_get_list_conference_cfp_no_url(self) -> None: + """Test reading conference with CFP end date but no CFP URL.""" + yaml_data = [ + { + "name": "PyCon", + "topic": "Python", + "location": "Portland", + "start": date(2024, 5, 15), + "end": date(2024, 5, 17), + "url": "https://pycon.org", + "cfp_end": date(2024, 2, 1), + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 2 + cfp_event = events[1] + assert cfp_event.url == "https://pycon.org" # Falls back to conference URL + + def test_get_list_multiple_conferences(self) -> None: + """Test reading multiple conferences from YAML.""" + yaml_data = [ + { + "name": "PyCon", + "topic": "Python", + "location": "Portland", + "start": date(2024, 5, 15), + "end": date(2024, 5, 17), + }, + { + "name": "EuroPython", + "topic": "Python", + "location": "Prague", + "start": date(2024, 7, 8), + "end": date(2024, 7, 14), + "cfp_end": date(2024, 3, 15), + }, + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 3 # 2 conferences + 1 CFP end + + # First conference + assert events[0].title == "PyCon (Portland)" + assert events[0].date == date(2024, 5, 15) + + # Second conference + assert events[1].title == "EuroPython (Prague)" + assert events[1].date == date(2024, 7, 8) + + # CFP end event for second conference + assert events[2].name == "cfp_end" + assert events[2].title == "CFP end: EuroPython (Prague)" + + def test_get_list_location_in_name(self) -> None: + """Test conference where location is already in name.""" + yaml_data = [ + { + "name": "PyCon Portland", + "topic": "Python", + "location": "Portland", + "start": date(2024, 5, 15), + "end": date(2024, 5, 17), + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 1 + assert events[0].title == "PyCon Portland" # No location appended + + def test_get_list_datetime_objects(self) -> None: + """Test reading conferences with datetime objects.""" + yaml_data = [ + { + "name": "PyCon", + "topic": "Python", + "location": "Portland", + "start": datetime(2024, 5, 15, 9, 0), + "end": datetime(2024, 5, 17, 17, 0), + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 1 + event = events[0] + assert event.date == datetime(2024, 5, 15, 9, 0) + assert event.end_date == datetime(2024, 5, 17, 17, 0) + + def test_get_list_invalid_date_order(self) -> None: + """Test that conferences with end before start raise assertion error.""" + yaml_data = [ + { + "name": "Invalid Conference", + "topic": "Testing", + "location": "Nowhere", + "start": date(2024, 5, 17), + "end": date(2024, 5, 15), # End before start + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + with pytest.raises(AssertionError): + get_list(f.name) + + def test_get_list_too_long_conference(self) -> None: + """Test that conferences longer than MAX_CONF_DAYS raise assertion error.""" + yaml_data = [ + { + "name": "Too Long Conference", + "topic": "Testing", + "location": "Nowhere", + "start": date(2024, 5, 1), + "end": date(2024, 6, 1), # More than 20 days + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + with pytest.raises(AssertionError): + get_list(f.name) + + def test_get_list_empty_file(self) -> None: + """Test reading empty YAML file.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump([], f) + f.flush() + + events = get_list(f.name) + + assert events == [] + + def test_get_list_same_day_conference(self) -> None: + """Test conference that starts and ends on same day.""" + yaml_data = [ + { + "name": "One Day Conference", + "topic": "Testing", + "location": "Test City", + "start": date(2024, 5, 15), + "end": date(2024, 5, 15), + } + ] + + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + yaml.dump(yaml_data, f) + f.flush() + + events = get_list(f.name) + + assert len(events) == 1 + event = events[0] + assert event.date == date(2024, 5, 15) + assert event.end_date == date(2024, 5, 15)