"""Announcing a scheduled match — ARCH-003. The same twenty lines lived in three routes. They had already drifted: create_match formatted the time from the values it had just parsed and printed 'TBD' whenever either end was missing, while edit_match re-derived them from the saved row and substituted the start time for a missing end. A match with a start and no end therefore announced a time in one route and 'TBD' in the other, for the same data. app/services/scheduling.py keeps the more careful of the two rules. """ from datetime import date, time import pytest from app.services import scheduling from app.services.scheduling import TIME_UNKNOWN, format_event_time, zip_participants class TestTimeFormatting: def test_a_full_range_is_rendered(self): assert format_event_time(time(18, 0), time(20, 30)) == '06:00 PM - 08:30 PM' def test_a_missing_end_falls_back_to_the_start(self): """This is the behaviour create_match did not have: it printed TBD and lost the start time it knew perfectly well.""" assert format_event_time(time(18, 0), None) == '06:00 PM - 06:00 PM' def test_no_start_means_nothing_useful_to_say(self): assert format_event_time(None, time(20, 0)) == TIME_UNKNOWN assert format_event_time(None, None) == TIME_UNKNOWN class TestParticipantPairing: def test_players_pair_with_their_participant_rows(self): assert list(zip_participants([7, 8, 9], [70, 80, 90])) == [(7, 70), (8, 80), (9, 90)] def test_a_short_participant_list_yields_none(self): """The routes append to two lists in step; if they ever fall out of step the notification still goes out, against the match itself.""" assert list(zip_participants([7, 8], [70])) == [(7, 70), (8, None)] def test_no_players_means_no_pairs(self): assert list(zip_participants([], [])) == [] class TestNotifying: @pytest.fixture def sent(self, monkeypatch): calls = [] monkeypatch.setattr( scheduling, 'send_schedule_notification', lambda **kwargs: calls.append(kwargs) ) return calls def test_each_participant_is_told(self, sent): count = scheduling.notify_participants( title='Scrim', date=date(2030, 4, 1), start_time=time(19, 0), end_time=time(21, 0), participants=[(1, 11), (2, 22)], fallback_id=99, ) assert count == 2 assert [call['user_id'] for call in sent] == [1, 2] assert [call['reference_id'] for call in sent] == [11, 22] assert {call['event_date'] for call in sent} == {'2030-04-01'} assert {call['event_time'] for call in sent} == {'07:00 PM - 09:00 PM'} def test_a_participant_without_a_row_falls_back_to_the_match(self, sent): """A Discord reaction resolves the reference back to a record; the match id is the one thing that always exists.""" scheduling.notify_participants( title='Scrim', date=date(2030, 4, 1), start_time=None, end_time=None, participants=[(1, None)], fallback_id=99, ) assert sent[0]['reference_id'] == 99 assert sent[0]['event_time'] == TIME_UNKNOWN def test_a_missing_player_is_skipped(self, sent): assert ( scheduling.notify_participants( title='Scrim', date=date(2030, 4, 1), start_time=None, end_time=None, participants=[(None, 11), (2, 22)], fallback_id=99, ) == 1 ) assert [call['user_id'] for call in sent] == [2]