165 lines
5.3 KiB
Python
165 lines
5.3 KiB
Python
|
|
"""
|
||
|
|
Tests for weekly repeating capacity.
|
||
|
|
|
||
|
|
A single capacity row is created for the nearest future Thursday, repeating
|
||
|
|
every 7 days. The tests verify:
|
||
|
|
- each Thursday in the series accepts bookings
|
||
|
|
- days that are not Thursday are rejected
|
||
|
|
- a slot that has reached capacity (capacity=1) is rejected on re-submission
|
||
|
|
|
||
|
|
Fixture chain (module-scoped):
|
||
|
|
repeating_timetable_id -> repeating_capacity_id
|
||
|
|
repeating_timetable_id -> repeating_form_id
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
from datetime import date, timedelta
|
||
|
|
import requests
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
BASE_URL = os.environ.get("WP_BASE_URL", "http://localhost/wordpress")
|
||
|
|
API = f"{BASE_URL}/wp-json/reservations/v1"
|
||
|
|
|
||
|
|
|
||
|
|
def _next_thursday() -> date:
|
||
|
|
"""Nearest Thursday at least 7 days from today."""
|
||
|
|
d = date.today() + timedelta(days=7)
|
||
|
|
while d.weekday() != 3: # 3 = Thursday
|
||
|
|
d += timedelta(days=1)
|
||
|
|
return d
|
||
|
|
|
||
|
|
|
||
|
|
THURSDAY = _next_thursday()
|
||
|
|
FRIDAY = THURSDAY + timedelta(days=1)
|
||
|
|
THURSDAY_WEEK_2 = THURSDAY + timedelta(weeks=1)
|
||
|
|
THURSDAY_WEEK_3 = THURSDAY + timedelta(weeks=2)
|
||
|
|
|
||
|
|
|
||
|
|
def slot(day: date, time: str = "09:00:00") -> str:
|
||
|
|
return f"{day.isoformat()}T{time}+00:00"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Fixtures
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
def repeating_timetable_id():
|
||
|
|
r = requests.post(f"{API}/timetable", json={
|
||
|
|
"name": "Weekly Thursday timetable",
|
||
|
|
"block_size": 60,
|
||
|
|
})
|
||
|
|
assert r.status_code == 201, r.text
|
||
|
|
return r.json()["id"]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
def repeating_capacity_id(repeating_timetable_id):
|
||
|
|
r = requests.post(f"{API}/timetable/{repeating_timetable_id}/capacity", json={
|
||
|
|
"capacity": 1,
|
||
|
|
"min_lead_time_minutes": 0,
|
||
|
|
"date": THURSDAY.isoformat(),
|
||
|
|
"start_time": "08:00",
|
||
|
|
"end_time": "18:00",
|
||
|
|
"repeat_period_in_days": 7,
|
||
|
|
"repeat_times": 52,
|
||
|
|
})
|
||
|
|
print(r.json())
|
||
|
|
assert r.status_code == 201, r.text
|
||
|
|
return r.json()["ids"][0]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="module")
|
||
|
|
def repeating_form_id(repeating_timetable_id):
|
||
|
|
r = requests.post(f"{API}/form-definition", json={
|
||
|
|
"name": "Weekly Thursday form",
|
||
|
|
"definition": {
|
||
|
|
"email_key": "email",
|
||
|
|
"elements": [
|
||
|
|
{
|
||
|
|
"type": "reservation",
|
||
|
|
"name": "reservation",
|
||
|
|
"label": "Book a slot",
|
||
|
|
"calendar_id": str(repeating_timetable_id),
|
||
|
|
"required": True,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"type": "input-text",
|
||
|
|
"name": "email",
|
||
|
|
"label": "Email",
|
||
|
|
"required": True,
|
||
|
|
"validation": "email",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"type": "button",
|
||
|
|
"name": "submit",
|
||
|
|
"label": "Submit",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
})
|
||
|
|
assert r.status_code == 201, r.text
|
||
|
|
return r.json()["id"]
|
||
|
|
|
||
|
|
|
||
|
|
def book(form_id, timetable_id, day: date, time: str = "09:00:00"):
|
||
|
|
return requests.post(f"{API}/form/{form_id}", json={
|
||
|
|
"email": "user@example.com",
|
||
|
|
"reservation": {
|
||
|
|
"timetable_id": timetable_id,
|
||
|
|
"timetable_reservations": [slot(day, time)],
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
|
||
|
|
def assert_not_available(r):
|
||
|
|
body = r.json()
|
||
|
|
assert body["success"] is False, f"Expected failure but got: {body}"
|
||
|
|
error = next((e for e in body["errors"] if e["element"] == "reservation"), None)
|
||
|
|
assert error is not None
|
||
|
|
assert error["code"] == "not_available"
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Tests
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
class TestRepeatingCapacity:
|
||
|
|
def test_first_thursday_is_available(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, THURSDAY)
|
||
|
|
assert r.json()["success"] is True, r.text
|
||
|
|
|
||
|
|
def test_first_thursday_is_full_after_booking(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
# capacity=1; the slot booked above is now exhausted
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, THURSDAY)
|
||
|
|
assert_not_available(r)
|
||
|
|
|
||
|
|
def test_friday_is_not_available(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, FRIDAY)
|
||
|
|
assert_not_available(r)
|
||
|
|
|
||
|
|
def test_second_thursday_is_available(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, THURSDAY_WEEK_2)
|
||
|
|
assert r.json()["success"] is True, r.text
|
||
|
|
|
||
|
|
def test_third_thursday_is_available(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, THURSDAY_WEEK_3)
|
||
|
|
assert r.json()["success"] is True, r.text
|
||
|
|
|
||
|
|
def test_wednesday_before_second_thursday_is_not_available(
|
||
|
|
self, repeating_form_id, repeating_timetable_id, repeating_capacity_id
|
||
|
|
):
|
||
|
|
wednesday = THURSDAY_WEEK_2 - timedelta(days=1)
|
||
|
|
r = book(repeating_form_id, repeating_timetable_id, wednesday)
|
||
|
|
assert_not_available(r)
|