"""Tests for recurring booking endpoints.""" from datetime import date, timedelta import pytest from fastapi.testclient import TestClient from sqlalchemy.orm import Session from app.models.booking import Booking def test_create_recurring_booking_success( client: TestClient, user_token: str, test_space, db: Session ): """Test successful creation of recurring bookings.""" # Create 4 Monday bookings (4 weeks) start_date = date.today() + timedelta(days=7) # Next week # Find the next Monday while start_date.weekday() != 0: # 0 = Monday start_date += timedelta(days=1) end_date = start_date + timedelta(days=21) # 3 weeks later response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "10:00", "duration_minutes": 60, "title": "Weekly Team Sync", "description": "Regular team meeting", "recurrence_days": [0], # Monday only "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 201 data = response.json() assert data["total_requested"] == 4 assert data["total_created"] == 4 assert data["total_skipped"] == 0 assert len(data["created_bookings"]) == 4 assert len(data["skipped_dates"]) == 0 # Verify all bookings were created bookings = db.query(Booking).filter(Booking.title == "Weekly Team Sync").all() assert len(bookings) == 4 def test_create_recurring_booking_multiple_days( client: TestClient, user_token: str, test_space, db: Session ): """Test recurring booking on multiple days (Mon, Wed, Fri).""" start_date = date.today() + timedelta(days=7) # Find the next Monday while start_date.weekday() != 0: start_date += timedelta(days=1) end_date = start_date + timedelta(days=14) # 2 weeks response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "14:00", "duration_minutes": 90, "title": "MWF Sessions", "recurrence_days": [0, 2, 4], # Mon, Wed, Fri "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 201 data = response.json() # Could be 6 or 7 depending on whether start_date itself is included assert data["total_created"] >= 6 assert data["total_created"] <= 7 assert data["total_skipped"] == 0 def test_create_recurring_booking_skip_conflicts( client: TestClient, user_token: str, test_space, admin_token: str, db: Session ): """Test skipping conflicted dates.""" start_date = date.today() + timedelta(days=7) # Find the next Monday while start_date.weekday() != 0: start_date += timedelta(days=1) # Create a conflicting booking on the 2nd Monday conflict_date = start_date + timedelta(days=7) client.post( "/api/bookings", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_datetime": f"{conflict_date.isoformat()}T10:00:00", "end_datetime": f"{conflict_date.isoformat()}T11:00:00", "title": "Conflicting Booking", }, ) # Now create recurring booking that will hit the conflict end_date = start_date + timedelta(days=21) response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "10:00", "duration_minutes": 60, "title": "Weekly Recurring", "recurrence_days": [0], # Monday "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 201 data = response.json() assert data["total_requested"] == 4 assert data["total_created"] == 3 # One skipped assert data["total_skipped"] == 1 assert len(data["skipped_dates"]) == 1 assert data["skipped_dates"][0]["date"] == conflict_date.isoformat() assert "deja rezervat" in data["skipped_dates"][0]["reason"].lower() def test_create_recurring_booking_stop_on_conflict( client: TestClient, user_token: str, test_space, db: Session ): """Test stopping on first conflict.""" start_date = date.today() + timedelta(days=7) # Find the next Monday while start_date.weekday() != 0: start_date += timedelta(days=1) # Create a conflicting booking on the 2nd Monday conflict_date = start_date + timedelta(days=7) client.post( "/api/bookings", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_datetime": f"{conflict_date.isoformat()}T10:00:00", "end_datetime": f"{conflict_date.isoformat()}T11:00:00", "title": "Conflicting Booking", }, ) # Now create recurring booking with skip_conflicts=False end_date = start_date + timedelta(days=21) response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "10:00", "duration_minutes": 60, "title": "Weekly Recurring", "recurrence_days": [0], "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": False, # Stop on conflict }, ) assert response.status_code == 201 data = response.json() assert data["total_requested"] == 4 assert data["total_created"] == 1 # Only first one before conflict assert data["total_skipped"] == 1 def test_create_recurring_booking_max_occurrences( client: TestClient, user_token: str, test_space ): """Test limiting to 52 occurrences.""" start_date = date.today() + timedelta(days=1) # Request 52+ weeks but within 1 year limit (365 days) end_date = start_date + timedelta(days=364) # Within 1 year response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "10:00", "duration_minutes": 60, "title": "Long Recurring", "recurrence_days": [0], # Monday "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 201 data = response.json() # Should be capped at 52 occurrences assert data["total_requested"] <= 52 assert data["total_created"] <= 52 def test_create_recurring_booking_validation(client: TestClient, user_token: str): """Test validation (invalid days, date range, etc.).""" start_date = date.today() + timedelta(days=7) end_date = start_date + timedelta(days=14) # Invalid recurrence day (> 6) response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": 1, "start_time": "10:00", "duration_minutes": 60, "title": "Invalid Day", "recurrence_days": [0, 7], # 7 is invalid "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 422 assert "Days must be 0-6" in response.json()["detail"][0]["msg"] # End date before start date response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": 1, "start_time": "10:00", "duration_minutes": 60, "title": "Invalid Range", "recurrence_days": [0], "start_date": end_date.isoformat(), "end_date": start_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 422 assert "end_date must be after start_date" in response.json()["detail"][0]["msg"] # Date range > 1 year response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": 1, "start_time": "10:00", "duration_minutes": 60, "title": "Too Long", "recurrence_days": [0], "start_date": start_date.isoformat(), "end_date": (start_date + timedelta(days=400)).isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 422 assert "cannot exceed 1 year" in response.json()["detail"][0]["msg"] def test_create_recurring_booking_invalid_time_format( client: TestClient, user_token: str, test_space ): """Test invalid time format.""" start_date = date.today() + timedelta(days=7) end_date = start_date + timedelta(days=14) # Test with malformed time string response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": test_space.id, "start_time": "abc", # Invalid format "duration_minutes": 60, "title": "Invalid Time", "recurrence_days": [0], "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 400 assert "Invalid start_time format" in response.json()["detail"] def test_create_recurring_booking_space_not_found( client: TestClient, user_token: str ): """Test recurring booking with non-existent space.""" start_date = date.today() + timedelta(days=7) end_date = start_date + timedelta(days=14) response = client.post( "/api/bookings/recurring", headers={"Authorization": f"Bearer {user_token}"}, json={ "space_id": 99999, # Non-existent "start_time": "10:00", "duration_minutes": 60, "title": "Test", "recurrence_days": [0], "start_date": start_date.isoformat(), "end_date": end_date.isoformat(), "skip_conflicts": True, }, ) assert response.status_code == 404 assert "Space not found" in response.json()["detail"]