"""Tests for slack-bot handle_swap_accept / handle_swap_decline.""" from freezegun import freeze_time # Monday 2026-06-01 08:00 ET — weekday, active shift is night, before 17:00 start. MON = "2026-06-01 12:00:00" # Same Monday but 18:00 ET — the night shift has already started. MON_AFTER_START = "2026-06-01 22:00:00" REQ = {"extension": "114", "name": "Alice", "slack_user_id": "U_ALICE"} TGT = {"extension": "115", "name": "Bob", "slack_user_id": "U_BOB"} def _accept_body(date_str, user_id="U_BOB", suffix=""): return { "actions": [{"action_id": f"swap_accept_{date_str}{suffix}"}], "user": {"id": user_id}, "channel": {"id": "D1"}, } def _decline_body(date_str, user_id="U_BOB", suffix=""): return { "actions": [{"action_id": f"swap_decline_{date_str}{suffix}"}], "user": {"id": user_id}, "channel": {"id": "D1"}, } def _pending(schedule, date_str, shift_type="night", target=TGT): schedule.create_pending_swap(date_str, shift_type, REQ, target, 1_900_000_000) def _resolved_text(respond): return respond.call_args.kwargs["blocks"][0]["text"]["text"] def _channels(client): return [c.kwargs.get("channel") for c in client.chat_postMessage.call_args_list] class TestAccept: @freeze_time(MON) def test_applies_override_marks_verified_notifies( self, slackbot_app, schedule, seed, respond, client, routing_spy ): # Requester holds the shift via the weekly schedule (production # invariant: swap-create validates the requester is the holder). seed.weekly("Monday", "114", "Alice") _pending(schedule, "2026-06-01") slackbot_app.handle_swap_accept( _accept_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert schedule.get_override("2026-06-01")["extension"] == "115" assert schedule.get_swap("2026-06-01")["status"] == "verified" # Today + active night → 3CX repointed to the new holder. routing_spy.assert_called_once_with("115") # Channel notification + requester DM both sent. assert "C_TEST" in _channels(client) and "U_ALICE" in _channels(client) assert "now covering" in _resolved_text(respond).lower() @freeze_time(MON) def test_future_shift_no_3cx( self, slackbot_app, schedule, seed, respond, client, routing_spy ): seed.weekly("Wednesday", "114", "Alice") _pending(schedule, "2026-06-03") # Wednesday slackbot_app.handle_swap_accept( _accept_body("2026-06-03"), respond, client, schedule, "C_TEST" ) assert schedule.get_override("2026-06-03")["extension"] == "115" routing_spy.assert_not_called() @freeze_time(MON) def test_wrong_clicker_rejected( self, slackbot_app, schedule, respond, client, routing_spy ): _pending(schedule, "2026-06-01") slackbot_app.handle_swap_accept( _accept_body("2026-06-01", user_id="U_STRANGER"), respond, client, schedule, "C_TEST", ) assert schedule.get_override("2026-06-01") is None assert "no longer valid" in _resolved_text(respond).lower() routing_spy.assert_not_called() @freeze_time(MON) def test_no_pending_swap(self, slackbot_app, schedule, respond, client): slackbot_app.handle_swap_accept( _accept_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert "no longer valid" in _resolved_text(respond).lower() @freeze_time(MON) def test_already_verified_swap_not_reapplied( self, slackbot_app, schedule, respond, client ): _pending(schedule, "2026-06-01") schedule.mark_swap_verified("2026-06-01") slackbot_app.handle_swap_accept( _accept_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert "no longer valid" in _resolved_text(respond).lower() assert schedule.get_override("2026-06-01") is None @freeze_time(MON_AFTER_START) def test_expired_after_shift_start( self, slackbot_app, schedule, respond, client, routing_spy ): _pending(schedule, "2026-06-01") slackbot_app.handle_swap_accept( _accept_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert "expired" in _resolved_text(respond).lower() assert schedule.get_override("2026-06-01") is None assert schedule.get_swap("2026-06-01") is None # cleared routing_spy.assert_not_called() @freeze_time(MON) def test_aborts_if_requester_no_longer_holder( self, slackbot_app, schedule, seed, respond, client, routing_spy ): # RIHB-1 regression: requester held via weekly, swap pending to Bob, # then the holder changes to Carol (e.g. weekly edit / admin reassign) # before Bob accepts. The accept must NOT steal the shift from Carol. seed.weekly("Monday", "114", "Alice") _pending(schedule, "2026-06-01") # Holder is now Carol (116), not the requester Alice (114). seed.weekly("Monday", "116", "Carol") slackbot_app.handle_swap_accept( _accept_body("2026-06-01"), respond, client, schedule, "C_TEST" ) # No override written to Bob; the swap is cleared and 3CX untouched. assert schedule.get_override("2026-06-01") is None assert "no longer assigned" in _resolved_text(respond).lower() routing_spy.assert_not_called() @freeze_time(MON) def test_weekend_day_suffix(self, slackbot_app, schedule, seed, respond, client): seed.weekly("Saturday", "114", "Alice", shift_type="day") _pending(schedule, "2026-06-06", shift_type="day") slackbot_app.handle_swap_accept( _accept_body("2026-06-06", suffix="_day"), respond, client, schedule, "C_TEST", ) assert schedule.get_override("2026-06-06", "day")["extension"] == "115" assert schedule.get_swap("2026-06-06", "day")["status"] == "verified" class TestDecline: @freeze_time(MON) def test_clears_and_dms_requester(self, slackbot_app, schedule, respond, client): _pending(schedule, "2026-06-01") slackbot_app.handle_swap_decline( _decline_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert schedule.get_swap("2026-06-01") is None assert schedule.get_override("2026-06-01") is None # never applied assert "declined" in _resolved_text(respond).lower() assert "U_ALICE" in _channels(client) # requester notified @freeze_time(MON) def test_wrong_clicker_rejected(self, slackbot_app, schedule, respond, client): _pending(schedule, "2026-06-01") slackbot_app.handle_swap_decline( _decline_body("2026-06-01", user_id="U_STRANGER"), respond, client, schedule, "C_TEST", ) assert "no longer valid" in _resolved_text(respond).lower() assert schedule.get_swap("2026-06-01") is not None # untouched @freeze_time(MON) def test_no_pending_swap(self, slackbot_app, schedule, respond, client): slackbot_app.handle_swap_decline( _decline_body("2026-06-01"), respond, client, schedule, "C_TEST" ) assert "no longer valid" in _resolved_text(respond).lower()