Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions services/car_renting_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from zoneinfo import ZoneInfo
from fastapi import HTTPException, status
from api_schemas.car_booking_schema import CarBookingCreate, CarBookingUpdate
from db_models.council_model import Council_DB
Expand Down Expand Up @@ -107,15 +108,9 @@ def create_new_booking(
if data.start_time < datetime.now(UTC):
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking start time cannot be in the past.")

if not manage_permission:
# Unconfirm booking between 17:00 and 08:00
if data.start_time.hour < 8 or data.start_time.hour >= 17:
booking_confirmed = False
if data.end_time.hour < 8 or data.end_time.hour >= 17:
booking_confirmed = False
# Unconfirm booking on weekends
if data.start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday
booking_confirmed = False
# Unconfirm personal bookings /Vic, BilF 25/26
if not manage_permission and data.personal:
booking_confirmed = False

# Require council_id if not personal booking
if not data.personal and data.council_id is None:
Expand Down Expand Up @@ -172,10 +167,6 @@ def booking_update(
else:
booking_confirmed = car_booking.confirmed

# Automagically assume the user wants the booking confirmed, they should not have manual control unlike admins
if not manage_permission and booking_confirmed == False:
booking_confirmed = True

# only check for illegal overlap if new times are provided
if data.start_time is not None or data.end_time is not None:
# Use new values if provided, otherwise use existing values
Expand All @@ -196,20 +187,40 @@ def booking_update(
if booking_overlaps:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Booking overlaps with another booking.")

if not manage_permission:
# unconfirms personal bookings that now fall outside of school hours
if not manage_permission and (data.personal or data.personal is None and car_booking.personal):

stockholm_tz = ZoneInfo("Europe/Stockholm")

# Unconfirm booking between 17:00 and 08:00
if data.start_time is not None:
if data.start_time.hour < 8 or data.start_time.hour >= 17:
sthlm_start_time = data.start_time.astimezone(stockholm_tz)
if sthlm_start_time.hour < 8 or sthlm_start_time.hour >= 17:
booking_confirmed = False
# Unconfirm booking on weekends
if data.start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday
if sthlm_start_time.weekday() >= 5: # 5 = Saturday, 6 = Sunday
booking_confirmed = False
# Unconfirm large movements of times
if abs((data.start_time - car_booking.start_time).total_seconds()) >= 24 * 3600: # 24h
booking_confirmed = False
if data.end_time is not None:
if data.end_time.hour < 8 or data.end_time.hour >= 17:
sthlm_end_time = data.end_time.astimezone(stockholm_tz)
if sthlm_end_time.hour < 8 or sthlm_end_time.hour >= 17:
booking_confirmed = False
if sthlm_end_time.weekday() >= 5:
booking_confirmed = False
if data.end_time.weekday() >= 5:
if abs((data.end_time - car_booking.end_time).total_seconds()) >= 24 * 3600: # 24h
booking_confirmed = False

# Unconfirm booking of > 24h, unless the previous was > 20h (allowing for a little bit of editing)
check_start_time = data.start_time if data.start_time is not None else car_booking.start_time
check_end_time = data.end_time if data.end_time is not None else car_booking.end_time

if (check_end_time - check_start_time).total_seconds() > 24 * 3600 and (
car_booking.end_time - car_booking.start_time
).total_seconds() <= 20 * 3600:
booking_confirmed = False

# Remove council_id if personal booking
if data.personal and data.council_id is not None:
data.council_id = None
Expand Down
175 changes: 153 additions & 22 deletions tests/test_car_bookings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def patch_booking(client, token, booking_id, **kwargs):
return client.patch(f"/car/{booking_id}", json=kwargs, headers=auth_headers(token))


def test_admin_autoconfirm(client, admin_token, admin_council_id):
def test_admin_autoconfirm_council(client, admin_token, admin_council_id):
start = stockholm_dt(2030, 1, 8, 10) # Tuesday
end = stockholm_dt(2030, 1, 8, 12)
resp = create_booking(client, admin_token, start, end, "admin booking", council_id=admin_council_id, personal=True)
resp = create_booking(client, admin_token, start, end, "admin booking", council_id=admin_council_id)
assert resp.status_code in (200, 201)
data = resp.json()
assert "confirmed" in data
Expand All @@ -59,36 +59,63 @@ def test_admin_autoconfirm(client, admin_token, admin_council_id):
assert resp4.json()["confirmed"] is True


def test_user_autoconfirm(client, member_token, member_council_id):
def test_admin_autoconfirm_personal(client, admin_token, admin_council_id):
start = stockholm_dt(2030, 1, 8, 10) # Tuesday
end = stockholm_dt(2030, 1, 8, 12)
resp = create_booking(client, admin_token, start, end, "admin booking", personal=True)
assert resp.status_code in (200, 201)
data = resp.json()
assert "confirmed" in data
assert "booking_id" in data
assert data["confirmed"] is True

start = stockholm_dt(2030, 1, 8, 6) # Before office hours
end = stockholm_dt(2030, 1, 8, 7)
resp2 = create_booking(client, admin_token, start, end, "early booking", personal=True)
assert resp2.status_code in (200, 201)
assert resp2.json()["confirmed"] is True

start = stockholm_dt(2030, 1, 8, 18) # After office hours
end = stockholm_dt(2030, 1, 8, 19)
resp3 = create_booking(client, admin_token, start, end, "late booking", personal=True)
assert resp3.status_code in (200, 201)
assert resp3.json()["confirmed"] is True

start = stockholm_dt(2030, 1, 12, 10) # Saturday
end = stockholm_dt(2030, 1, 12, 12)
resp4 = create_booking(client, admin_token, start, end, "weekend booking", personal=True)
assert resp4.status_code in (200, 201)
assert resp4.json()["confirmed"] is True


def test_user_autoconfirm_council(client, member_token, member_council_id):
# Book inside hours
start = stockholm_dt(2030, 1, 8, 10)
end = stockholm_dt(2030, 1, 8, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id)
assert resp.status_code in (200, 201)
assert resp.json()["confirmed"] is True


def test_user_autounconfirm_outside_hours(client, member_token, member_council_id):
# Before 08:00
start = stockholm_dt(2030, 1, 8, 7)
end = stockholm_dt(2030, 1, 8, 9)
resp = create_booking(client, member_token, start, end, "early booking", council_id=member_council_id)
# Saturday
start = stockholm_dt(2030, 1, 12, 10)
end = stockholm_dt(2030, 1, 12, 12)
resp = create_booking(client, member_token, start, end, "personal booking", council_id=member_council_id)
assert resp.status_code in (200, 201)
assert resp.json()["confirmed"] is False
assert resp.json()["confirmed"] is True

# After 17:00
start = stockholm_dt(2030, 1, 8, 18)
end = stockholm_dt(2030, 1, 8, 19)
resp = create_booking(client, member_token, start, end, "late booking", council_id=member_council_id)

def test_user_autounconfirm_personal(client, member_token, member_council_id):
# Book inside hours
start = stockholm_dt(2030, 1, 8, 10)
end = stockholm_dt(2030, 1, 8, 12)
resp = create_booking(client, member_token, start, end, "user booking", personal=True)
assert resp.status_code in (200, 201)
assert resp.json()["confirmed"] is False


def test_user_autounconfirm_weekend(client, member_token, member_council_id):
# Saturday
start = stockholm_dt(2030, 1, 12, 10)
end = stockholm_dt(2030, 1, 12, 12)
resp = create_booking(client, member_token, start, end, "weekend booking", council_id=member_council_id)
resp = create_booking(client, member_token, start, end, "personal booking", personal=True)
assert resp.status_code in (200, 201)
assert resp.json()["confirmed"] is False

Expand All @@ -112,17 +139,122 @@ def test_admin_can_confirm_unconfirm(client, admin_token, admin_council_id):
assert resp3.json()["confirmed"] is True


def test_user_edit_autounconfirm(client, member_token, member_council_id):
def test_user_edit_autounconfirm_council(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id)
booking_id = resp.json()["booking_id"]
assert resp.json()["confirmed"] is True

# Try to edit to outside hours, should stay confirmed
new_end = stockholm_dt(2030, 1, 10, 18)
resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat())
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is True


def test_user_edit_autounconfirm_personal(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", personal=True)
booking_id = resp.json()["booking_id"]
assert resp.json()["confirmed"] is False

# Admin confirms
resp2 = patch_booking(client, member_token, booking_id, confirmed=True)
assert resp2.json()["confirmed"] is True

# Try to edit to outside hours
new_end = stockholm_dt(2030, 1, 10, 18)
resp2 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat())
assert resp2.status_code == 200
assert resp2.json()["confirmed"] is False
resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat())
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is False


def test_user_edit_keep_confirmed_in_hours(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True)
booking_id = resp.json()["booking_id"]

assert resp.json()["confirmed"] is False

# Admin confirms
resp2 = patch_booking(client, member_token, booking_id, confirmed=True)
assert resp2.json()["confirmed"] is True

# Try to edit, keep inside hours
new_end = stockholm_dt(2030, 1, 10, 16)
resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat())
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is True


# Test that movements larger than one week for a personal booking get unconfirmed
def test_personal_booking_large_movement_unconfirms(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True)
booking_id = resp.json()["booking_id"]

assert resp.json()["confirmed"] is False

# Admin confirms
resp2 = patch_booking(client, member_token, booking_id, confirmed=True)
assert resp2.json()["confirmed"] is True

# Try to edit, move more than a week, should become unconfirmed
new_start = stockholm_dt(2030, 1, 17, 10)
new_end = stockholm_dt(2030, 1, 17, 12)
resp3 = patch_booking(
client, member_token, booking_id, end_time=new_end.isoformat(), start_time=new_start.isoformat()
)
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is False


# Test autounconfirm of multi day bookings
def test_personal_booking_multi_day_unconfirms(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True)
booking_id = resp.json()["booking_id"]

assert resp.json()["confirmed"] is False

# Admin confirms
resp2 = patch_booking(client, member_token, booking_id, confirmed=True)
assert resp2.json()["confirmed"] is True

# Try to edit, really long booking, should unconfirm
new_start = stockholm_dt(2030, 1, 10, 10)
new_end = stockholm_dt(2030, 1, 11, 12)
resp3 = patch_booking(
client, member_token, booking_id, end_time=new_end.isoformat(), start_time=new_start.isoformat()
)
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is False


def test_user_edit_keep_unconfirmed_in_hours(client, member_token, member_council_id):
# User books inside hours
start = stockholm_dt(2030, 1, 10, 10)
end = stockholm_dt(2030, 1, 10, 12)
resp = create_booking(client, member_token, start, end, "user booking", council_id=member_council_id, personal=True)
booking_id = resp.json()["booking_id"]

assert resp.json()["confirmed"] is False

# Try to edit, keep inside hours, should remain unconfirmed
new_end = stockholm_dt(2030, 1, 10, 16)
resp3 = patch_booking(client, member_token, booking_id, end_time=new_end.isoformat())
assert resp3.status_code == 200
assert resp3.json()["confirmed"] is False


def test_overlapping_bookings_not_allowed(client, member_token, member_council_id):
Expand Down Expand Up @@ -402,4 +534,3 @@ def test_admin_delete_any_booking(client, admin_token, member_token, member_coun
# Admin should be able to delete the member's booking
resp2 = client.delete(f"/car/{booking_id}", headers=auth_headers(admin_token))
assert resp2.status_code in (200, 204)

Loading