"""Tests for slack-bot admin holiday subcommands (add / remove / list) and the one-off EventBridge Scheduler lifecycle (Phase 7).""" import boto3 import pytest from freezegun import freeze_time # Wednesday 2026-07-01 12:00 ET — well before the 2026-07-04 holiday. WED = "2026-07-01 16:00:00" # Saturday 2026-07-04 12:00 ET — inside the holiday window (08:00–17:00). SAT_MIDDAY = "2026-07-04 16:00:00" ROLE_ARN = "arn:aws:iam::328440206208:role/holiday-sched" ROUTER_ARN = "arn:aws:lambda:us-east-1:328440206208:function:afterhours-holiday-router" @pytest.fixture def holiday_env(monkeypatch): """Wire the scheduler/router ARNs so schedule creation is exercised.""" monkeypatch.setenv("HOLIDAY_ROUTER_ARN", ROUTER_ARN) monkeypatch.setenv("HOLIDAY_SCHEDULER_ROLE_ARN", ROLE_ARN) monkeypatch.setenv("HOLIDAY_SCHEDULE_GROUP", "default") @pytest.fixture def scheduler(): return boto3.client("scheduler", region_name="us-east-1") def _admin(slackbot_app, schedule, text, respond, client, channel="C_TEST"): slackbot_app._handle_admin( respond, schedule, "U_ADMIN", text, True, client, channel ) def _schedule_names(scheduler): return {s["Name"] for s in scheduler.list_schedules()["Schedules"]} class TestAdd: @freeze_time(WED) def test_creates_record_schedules_and_announces( self, slackbot_app, schedule, dynamodb_table, holiday_env, scheduler, respond, client, text_of, monkeypatch, ): inline = _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 2 Independence Day", respond, client, ) holiday = schedule.get_holiday("2026-07-04") assert holiday is not None assert holiday["slots"] == 2 assert holiday["label"] == "Independence Day" assert float(holiday["multiplier"]) == 1.5 # CONFIG default assert set(holiday["schedule_names"]) == { "holiday-activate-20260704", "holiday-deactivate-20260704", } # Both one-off schedules exist in EventBridge Scheduler. assert _schedule_names(scheduler) >= set(holiday["schedule_names"]) # Future holiday → not inline-activated. inline.assert_not_called() # Channel announcement + confirmation. assert client.chat_postMessage.call_args.kwargs["channel"] == "C_TEST" assert "Scheduled" in text_of(respond) @freeze_time(WED) def test_custom_multiplier( self, slackbot_app, schedule, dynamodb_table, holiday_env, scheduler, respond, client, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 3 x2 Independence Day", respond, client, ) holiday = schedule.get_holiday("2026-07-04") assert float(holiday["multiplier"]) == 2.0 assert holiday["slots"] == 3 assert holiday["label"] == "Independence Day" @freeze_time(SAT_MIDDAY) def test_inline_activation_when_window_open( self, slackbot_app, schedule, dynamodb_table, holiday_env, scheduler, respond, client, monkeypatch, ): inline = _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 2 Independence Day", respond, client, ) # Added mid-window → activate now (the 08:00 schedule already passed). inline.assert_called_once() assert inline.call_args.args[1] == "2026-07-04" @freeze_time(WED) def test_duplicate_rolls_back_schedules( self, slackbot_app, schedule, seed, dynamodb_table, holiday_env, scheduler, respond, client, text_of, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) seed.holiday("2026-07-04", slots=1, label="Existing") _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 2 Independence Day", respond, client, ) assert "already exists" in text_of(respond).lower() # The schedules created for the duplicate attempt were deleted again. assert _schedule_names(scheduler) == set() @freeze_time(WED) def test_past_date_rejected( self, slackbot_app, schedule, holiday_env, respond, client, text_of, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-06-01 2 Old Day", respond, client, ) assert "past" in text_of(respond).lower() @freeze_time(WED) def test_bad_slots( self, slackbot_app, schedule, holiday_env, respond, client, text_of, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 lots Independence Day", respond, client, ) assert "whole number" in text_of(respond).lower() @freeze_time(WED) def test_missing_label( self, slackbot_app, schedule, holiday_env, respond, client, text_of, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 2 x2", respond, client, ) assert "label is required" in text_of(respond).lower() def test_usage(self, slackbot_app, schedule, respond, client, text_of): _admin(slackbot_app, schedule, "admin holiday add 2026-07-04", respond, client) assert "Usage" in text_of(respond) class TestRemove: @freeze_time(WED) def test_removes_record_and_outstanding_schedules( self, slackbot_app, schedule, dynamodb_table, holiday_env, scheduler, respond, client, text_of, monkeypatch, ): _spy_inline(slackbot_app, monkeypatch) _admin( slackbot_app, schedule, "admin holiday add 2026-07-04 2 Independence Day", respond, client, ) assert _schedule_names(scheduler) != set() _admin( slackbot_app, schedule, "admin holiday remove 2026-07-04", respond, client ) assert schedule.get_holiday("2026-07-04") is None assert _schedule_names(scheduler) == set() # outstanding schedules deleted assert "Removed" in text_of(respond) @freeze_time(WED) def test_no_holiday(self, slackbot_app, schedule, respond, client, text_of): _admin( slackbot_app, schedule, "admin holiday remove 2026-07-04", respond, client ) assert "No holiday" in text_of(respond) class TestList: @freeze_time(WED) def test_lists_upcoming_only( self, slackbot_app, schedule, seed, respond, client, text_of ): seed.holiday("2026-07-04", slots=2, label="Independence Day") seed.holiday("2026-12-25", slots=1, label="Christmas") seed.holiday("2026-01-01", slots=1, label="New Year") # past _admin(slackbot_app, schedule, "admin holiday list", respond, client) out = text_of(respond) assert "Independence Day" in out assert "Christmas" in out assert "New Year" not in out # past holiday filtered out @freeze_time(WED) def test_shows_filled_counts( self, slackbot_app, schedule, seed, respond, client, text_of ): seed.holiday( "2026-07-04", slots=2, label="Independence Day", assignees={"114": {"name": "Alice", "claimed_at": "x"}}, ) _admin(slackbot_app, schedule, "admin holiday list", respond, client) assert "1/2 filled" in text_of(respond) @freeze_time(WED) def test_empty(self, slackbot_app, schedule, respond, client, text_of): _admin(slackbot_app, schedule, "admin holiday list", respond, client) assert "No upcoming holidays" in text_of(respond) class TestDispatch: def test_unknown_holiday_command( self, slackbot_app, schedule, respond, client, text_of ): _admin(slackbot_app, schedule, "admin holiday frobnicate", respond, client) assert "Unknown holiday command" in text_of(respond) def _spy_inline(slackbot_app, monkeypatch): """Replace inline activation (a real Lambda invoke) with a spy.""" from unittest.mock import MagicMock spy = MagicMock(name="_activate_holiday_inline") monkeypatch.setattr(slackbot_app, "_activate_holiday_inline", spy) return spy