"""Tests for the roster-sync Lambda handler.""" from unittest.mock import MagicMock import pytest @pytest.fixture def env(monkeypatch): monkeypatch.setenv("TCX_SECRET_PREFIX", "afterhours-shift-manager/3cx-") @pytest.fixture def fake_3cx(rostersync_app, monkeypatch): """Patch ThreeCXClient + get_secret. Returns a setter for group members.""" client = MagicMock(name="ThreeCXClient") monkeypatch.setattr(rostersync_app, "ThreeCXClient", MagicMock(return_value=client)) monkeypatch.setattr(rostersync_app, "get_secret", lambda _id: "secret") def set_members(members): client.get_group_members.return_value = members return set_members def test_adds_removes_and_preserves_links( rostersync_app, schedule, seed, env, fake_3cx ): seed.roster("114", "Alice", slack_user_id="U_ALICE") seed.roster("115", "Bob", slack_user_id="U_BOB") # departs fake_3cx( [ {"Number": "114", "MemberName": "Alice", "Type": "Extension"}, {"Number": "116", "MemberName": "New Person", "Type": "Extension"}, { "Number": "117", "MemberName": "Voicemail", "Type": "Extension", }, # excluded { "Number": "118", "MemberName": "Reception", "Type": "RingGroup", }, # excluded ] ) result = rostersync_app.handler({"force": True}, None) # 116 added, 115 removed, 117/118 ignored. assert any("116" in a for a in result["added"]) assert any("115" in r for r in result["removed"]) assert schedule.get_employee_by_extension("116")["name"] == "New Person" assert schedule.get_employee_by_extension("115") is None assert schedule.get_employee_by_extension("117") is None # Existing link preserved. assert schedule.get_employee_by_extension("114")["slack_user_id"] == "U_ALICE" def test_renames_changed_member_preserving_link( rostersync_app, schedule, seed, env, fake_3cx ): seed.roster("114", "Alice", slack_user_id="U_ALICE") fake_3cx([{"Number": "114", "MemberName": "Alicia", "Type": "Extension"}]) result = rostersync_app.handler({"force": True}, None) assert any("114" in u for u in result["updated"]) emp = schedule.get_employee_by_extension("114") assert emp["name"] == "Alicia" assert emp["slack_user_id"] == "U_ALICE" def test_empty_3cx_result_does_not_wipe_roster( rostersync_app, schedule, seed, env, fake_3cx ): seed.roster("114", "Alice", slack_user_id="U_ALICE") fake_3cx([]) result = rostersync_app.handler({"force": True}, None) assert result["removed"] == [] assert schedule.get_employee_by_extension("114") is not None def test_skips_when_wrong_hour_and_not_forced(rostersync_app, env, fake_3cx): import freezegun # 12:00 UTC = 08:00 ET, not 6am → skip. with freezegun.freeze_time("2026-06-01 12:00:00"): assert rostersync_app.handler({}, None) == {"skipped": True}