Rate-limit public bookings per contact; add direct owner contact endpoint
Test backoffice (smb-crm) / test (push) Successful in 1m43s
Test backoffice (smb-crm) / test (push) Successful in 1m43s
Public booking API now rejects a 6th active booking from the same customer_contact within 24h (429), stopping one contact from filling every slot on every resource, while owner-entered manual bookings stay unaffected. Add POST /api/contact: client sites can reach their own owner's inbox directly (via their existing login email) for general inquiries, separate from the agency's leads/Telegram pipeline (n8n/lead-intake.json), which stays reserved for actual prospects contacting the agency itself. Paris Barber Shop's contact form and Rückruf widget now point here; the Rückruf floating widget itself has been removed from the site. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -185,6 +185,38 @@ def test_concurrent_booking_requests_only_one_succeeds(client):
|
||||
assert len(bdb.list_bookings(CLIENT_A)) == 1
|
||||
|
||||
|
||||
def test_contact_rate_limit_blocks_after_five_recent_bookings(client):
|
||||
resource, service = _setup_resource_and_service(
|
||||
min_notice_minutes=0, max_advance_days=365)
|
||||
day = _next_monday(date.today())
|
||||
slots = client.get("/api/booking/slots", query_string={
|
||||
"client_id": CLIENT_A, "resource_id": resource["resource_id"],
|
||||
"service_id": service["service_id"],
|
||||
"date_from": day.isoformat(), "date_to": day.isoformat()}).get_json()["slots"]
|
||||
assert len(slots) >= 6 # 09:00-17:00, 60min slots -- 8 available
|
||||
|
||||
for slot in slots[:5]:
|
||||
resp = client.post("/api/booking", json={
|
||||
"client_id": CLIENT_A, "resource_id": resource["resource_id"],
|
||||
"service_id": service["service_id"], "start_time": slot,
|
||||
"customer_name": "Serial Booker", "customer_contact": "serial@example.com"})
|
||||
assert resp.status_code == 201
|
||||
|
||||
resp = client.post("/api/booking", json={
|
||||
"client_id": CLIENT_A, "resource_id": resource["resource_id"],
|
||||
"service_id": service["service_id"], "start_time": slots[5],
|
||||
"customer_name": "Serial Booker", "customer_contact": "serial@example.com"})
|
||||
assert resp.status_code == 429
|
||||
assert len(bdb.list_bookings(CLIENT_A)) == 5
|
||||
|
||||
# A different contact is unaffected by the first contact's count.
|
||||
resp = client.post("/api/booking", json={
|
||||
"client_id": CLIENT_A, "resource_id": resource["resource_id"],
|
||||
"service_id": service["service_id"], "start_time": slots[5],
|
||||
"customer_name": "Someone Else", "customer_contact": "else@example.com"})
|
||||
assert resp.status_code == 201
|
||||
|
||||
|
||||
def test_cancel_with_valid_token_cancels_booking(client):
|
||||
resource, service = _setup_resource_and_service(
|
||||
min_notice_minutes=0, max_advance_days=365)
|
||||
|
||||
Reference in New Issue
Block a user