mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 05:33:12 +00:00
Local CI and the image deploy duplicated the org workflows and still required ci / ci. Pin the callers to those workflows and trust the reusable deploy ref.
192 lines
6.7 KiB
Python
192 lines
6.7 KiB
Python
"""Tests for portal shift mutations."""
|
|
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import freezegun
|
|
import pytest
|
|
|
|
from shared.portal_ops import (
|
|
ActionError,
|
|
admin_clear,
|
|
admin_holiday_add,
|
|
admin_open,
|
|
drop,
|
|
pick,
|
|
respond_swap,
|
|
snapshot,
|
|
swap,
|
|
)
|
|
from shared import side_effects as effects
|
|
|
|
ET = ZoneInfo("America/New_York")
|
|
|
|
|
|
@pytest.fixture
|
|
def quiet_slack(monkeypatch):
|
|
monkeypatch.setattr(effects, "slack_token", lambda: None)
|
|
monkeypatch.setattr(effects, "refresh_schedule_post", lambda *a, **k: None)
|
|
monkeypatch.setattr(effects, "post_shift_change", lambda *a, **k: None)
|
|
monkeypatch.setattr(effects, "dm_late_pickup_admins", lambda *a, **k: 1)
|
|
monkeypatch.setattr(effects, "dm_swap_request", lambda *a, **k: True)
|
|
monkeypatch.setattr(effects, "dm_text", lambda *a, **k: None)
|
|
monkeypatch.setattr(effects, "maybe_repoint_today", lambda *a, **k: False)
|
|
|
|
|
|
def _alice(schedule, seed):
|
|
seed.roster("114", "Alice", slack_user_id="U_ALICE", email="alice@seahavenind.com")
|
|
seed.roster("115", "Bob", slack_user_id="U_BOB", email="bob@seahavenind.com")
|
|
return schedule.get_employee_by_extension("114")
|
|
|
|
|
|
def _admin(schedule, seed):
|
|
employee = _alice(schedule, seed)
|
|
seed.config(admin_users=["U_ALICE"])
|
|
return employee
|
|
|
|
|
|
def test_pick_open_shift(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.open_shift("2026-06-10")
|
|
with freezegun.freeze_time("2026-06-08 12:00:00-04:00"):
|
|
result = pick(schedule, employee, "2026-06-10", "night")
|
|
assert result["ok"] is True
|
|
assert schedule.get_override("2026-06-10")["extension"] == "114"
|
|
|
|
|
|
def test_drop_lock(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.override("2026-06-10", "114", "Alice")
|
|
with freezegun.freeze_time("2026-06-10 12:00:00-04:00"):
|
|
with pytest.raises(ActionError) as err:
|
|
drop(schedule, employee, "2026-06-10", "night")
|
|
assert err.value.code == "DROP_LOCK"
|
|
|
|
|
|
def test_swap_creates_pending(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.override("2026-06-12", "114", "Alice")
|
|
with freezegun.freeze_time("2026-06-08 12:00:00-04:00"):
|
|
result = swap(schedule, employee, "2026-06-12", "115", "night")
|
|
assert result["ok"] is True
|
|
pending = schedule.get_swap("2026-06-12", "night")
|
|
assert pending["target_ext"] == "115"
|
|
snap = snapshot(schedule, schedule.get_employee_by_extension("115"))
|
|
assert snap["pendingSwaps"][0]["requesterExt"] == "114"
|
|
assert snap["pendingSwaps"][0]["incoming"] is True
|
|
mine = snapshot(schedule, employee)
|
|
assert mine["pendingSwaps"][0]["incoming"] is False
|
|
|
|
|
|
def test_late_pickup_creates_request(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.open_shift("2026-06-10")
|
|
with freezegun.freeze_time("2026-06-10 20:00:00-04:00"):
|
|
result = pick(schedule, employee, "2026-06-10", "night")
|
|
assert result["latePickup"] is True
|
|
req = schedule.get_pickup_request("2026-06-10", "night", "114")
|
|
assert req["status"] == "pending"
|
|
|
|
|
|
def test_snapshot_includes_weekday_holiday_day_shift(schedule, seed):
|
|
employee = _alice(schedule, seed)
|
|
seed.holiday(
|
|
"2026-06-10",
|
|
assignees={"114": {"name": "Alice", "claimed_at": "now"}},
|
|
)
|
|
with freezegun.freeze_time("2026-06-08 12:00:00-04:00"):
|
|
snap = snapshot(schedule, employee)
|
|
wednesday = next(day for day in snap["days"] if day["date"] == "2026-06-10")
|
|
assert wednesday["slots"][0]["kind"] == "holiday"
|
|
assert wednesday["slots"][0]["shiftType"] == "day"
|
|
assert wednesday["slots"][0]["mine"] is True
|
|
|
|
|
|
def test_drop_accepts_day_for_holiday_slot(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.holiday(
|
|
"2026-06-10",
|
|
assignees={"114": {"name": "Alice", "claimed_at": "now"}},
|
|
)
|
|
with freezegun.freeze_time("2026-06-08 06:00:00-04:00"):
|
|
result = drop(schedule, employee, "2026-06-10", "day")
|
|
assert result["ok"] is True
|
|
assert schedule.get_holiday("2026-06-10")["assignees"] == {}
|
|
|
|
|
|
def test_swap_accept_rechecks_resolved_holder(schedule, seed, quiet_slack):
|
|
employee = _alice(schedule, seed)
|
|
seed.roster("116", "Charlie", slack_user_id="U_CHARLIE")
|
|
seed.weekly("Friday", "114", "Alice")
|
|
with freezegun.freeze_time("2026-06-08 12:00:00-04:00"):
|
|
swap(schedule, employee, "2026-06-12", "115", "night")
|
|
seed.weekly("Friday", "116", "Charlie")
|
|
with pytest.raises(ActionError) as err:
|
|
respond_swap(
|
|
schedule,
|
|
schedule.get_employee_by_extension("115"),
|
|
"2026-06-12",
|
|
"night",
|
|
True,
|
|
)
|
|
assert err.value.code == "CONFLICT"
|
|
assert schedule.get_override("2026-06-12") is None
|
|
assert schedule.get_swap("2026-06-12", "night") is None
|
|
|
|
|
|
def test_admin_open_repoints_active_shift_to_fallback(
|
|
schedule, seed, quiet_slack, monkeypatch
|
|
):
|
|
employee = _admin(schedule, seed)
|
|
calls = []
|
|
monkeypatch.setattr(
|
|
effects,
|
|
"maybe_repoint_today",
|
|
lambda date, shift_type, extension: (
|
|
calls.append((date, shift_type, extension)) or True
|
|
),
|
|
)
|
|
with freezegun.freeze_time("2026-06-10 20:00:00-04:00"):
|
|
result = admin_open(schedule, employee, "2026-06-10", "night")
|
|
assert result["repointed"] is True
|
|
assert calls == [("2026-06-10", "night", "100")]
|
|
|
|
|
|
def test_admin_clear_repoints_active_shift_to_resolved_holder(
|
|
schedule, seed, quiet_slack, monkeypatch
|
|
):
|
|
employee = _admin(schedule, seed)
|
|
seed.weekly("Wednesday", "115", "Bob")
|
|
seed.override("2026-06-10", "114", "Alice")
|
|
calls = []
|
|
monkeypatch.setattr(
|
|
effects,
|
|
"maybe_repoint_today",
|
|
lambda date, shift_type, extension: (
|
|
calls.append((date, shift_type, extension)) or True
|
|
),
|
|
)
|
|
with freezegun.freeze_time("2026-06-10 20:00:00-04:00"):
|
|
result = admin_clear(schedule, employee, "2026-06-10", "night")
|
|
assert result["repointed"] is True
|
|
assert calls == [("2026-06-10", "night", "115")]
|
|
|
|
|
|
def test_invalid_holiday_multiplier_rejected_before_schedules(
|
|
schedule, seed, quiet_slack, monkeypatch
|
|
):
|
|
employee = _admin(schedule, seed)
|
|
monkeypatch.setattr(
|
|
effects, "create_holiday_schedules", lambda _date: pytest.fail("called")
|
|
)
|
|
with freezegun.freeze_time("2026-06-08 12:00:00-04:00"):
|
|
with pytest.raises(ActionError) as err:
|
|
admin_holiday_add(
|
|
schedule,
|
|
employee,
|
|
"2026-12-25",
|
|
2,
|
|
"Christmas",
|
|
"not-a-number",
|
|
)
|
|
assert err.value.code == "INVALID_MULTIPLIER"
|
|
assert schedule.get_holiday("2026-12-25") is None
|