| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- import pytest
- from datetime import date, timedelta
- from room_cal._utils import get_config, iter_dates, local_today, services as _services
- from room_cal.closed import ClosedData
- import room_cal.rules as _rules
- from room_cal.rules.closed.future_block import RULE_NAME, run as future_block_run
- # ── future_block (pure) ────────────────────────────────────────────────────────
- def _days_out() -> int:
- return get_config().rules.future_block.days_out
- @pytest.mark.asyncio
- async def test_future_block_rule_registered():
- assert RULE_NAME == 'future_block'
- assert 'future_block' in _rules.list_rules('closed')
- assert 'future_block' in _rules.list_pipelines(type_='closed')
- @pytest.mark.asyncio
- async def test_future_block_beyond_threshold_all_closed():
- """Every returned date strictly beyond today + days_out should have closed=True.
- Dates already blocked by a reservation or Closed event are omitted."""
- days_out = _days_out()
- start = local_today() + timedelta(days=days_out + 1)
- end = start + timedelta(days=4)
- result = await future_block_run(start, end, {})
- assert set(result).issubset(set(iter_dates(start, end)))
- assert all(bool(rec.services) for rec in result.values())
- @pytest.mark.asyncio
- async def test_future_block_within_threshold_all_open():
- """Every date on or before today + days_out should have closed=False."""
- days_out = _days_out()
- start = local_today() + timedelta(days=1)
- end = local_today() + timedelta(days=min(days_out - 1, 5))
- result = await future_block_run(start, end, {})
- assert all(not rec.services for rec in result.values())
- @pytest.mark.asyncio
- async def test_future_block_threshold_boundary():
- """The threshold date itself is not closed; the day after is.
- Dates already blocked on the calendar are omitted from results."""
- days_out = _days_out()
- threshold = local_today() + timedelta(days=days_out)
- result = await future_block_run(threshold - timedelta(days=1), threshold + timedelta(days=1), {})
- if threshold in result:
- assert not result[threshold].services
- if threshold + timedelta(days=1) in result:
- assert bool(result[threshold + timedelta(days=1)].services)
- @pytest.mark.asyncio
- async def test_future_block_covers_full_range():
- """run() returns at most one record per date, in range, with no duplicates."""
- days_out = _days_out()
- start = local_today() + timedelta(days=days_out - 2)
- end = start + timedelta(days=5)
- result = await future_block_run(start, end, {})
- assert set(result).issubset(set(iter_dates(start, end)))
- assert len(result) == len(set(result))
- # ── apply_rules — future_block (live CalDAV) ───────────────────────────────────
- @pytest.mark.asyncio
- async def test_apply_rules_future_block_creates_closed_events():
- """apply_rules with future_block writes Closed events for dates beyond the threshold."""
- from room_cal.actions.apply_rules import apply_rules
- from room_cal.closed import Closed
- closed_cal = Closed()
- days_out = _days_out()
- start = local_today() + timedelta(days=days_out + 10)
- end = start + timedelta(days=2)
- try:
- result = await apply_rules(start, end, pipeline_name='future_block')
- assert 'future_block' in result
- assert set(result['future_block']).issubset(set(iter_dates(start, end)))
- for d in iter_dates(start, end):
- svcs = await closed_cal.get(d)
- assert svcs is not None and svcs.services, f"Expected Closed event on {d}"
- svc_keys = sorted(s.key for s in _services())
- for d in iter_dates(start, end):
- assert sorted((await closed_cal.get(d)).services) == svc_keys
- finally:
- for d in iter_dates(start, end):
- await closed_cal.set(d, ClosedData(services=[]))
- @pytest.mark.asyncio
- async def test_apply_rules_future_block_removes_stale_closed_events():
- """Rule-sourced Closed events on dates now inside the threshold are removed on re-run."""
- from room_cal.actions.apply_rules import apply_rules
- from room_cal.closed import Closed
- closed_cal = Closed()
- svc_keys = [s.key for s in _services()]
- near_date = local_today() + timedelta(days=5)
- source = {'domain': 'rules', 'name': 'Rule =>', 'id': 'future_block'}
- await closed_cal.set(near_date, ClosedData(services=svc_keys, source=source))
- try:
- assert await closed_cal.get(near_date) is not None
- result = await apply_rules(near_date, near_date, pipeline_name='future_block')
- assert result['future_block'] == []
- assert await closed_cal.get(near_date) is None, f"Stale Closed event not removed on {near_date}"
- finally:
- await closed_cal.set(near_date, ClosedData(services=[]))
- @pytest.mark.asyncio
- async def test_apply_rules_future_block_idempotent():
- """Running apply_rules twice on the same beyond-threshold range is idempotent."""
- from room_cal.actions.apply_rules import apply_rules
- from room_cal.closed import Closed
- closed_cal = Closed()
- days_out = _days_out()
- start = local_today() + timedelta(days=days_out + 20)
- end = start + timedelta(days=1)
- try:
- result1 = await apply_rules(start, end, pipeline_name='future_block')
- result2 = await apply_rules(start, end, pipeline_name='future_block')
- assert result1 == result2
- for d in iter_dates(start, end):
- svcs = await closed_cal.get(d)
- assert svcs is not None and svcs.services
- finally:
- for d in iter_dates(start, end):
- await closed_cal.set(d, ClosedData(services=[]))
|