Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
martcl committed Mar 19, 2024
2 parents 8347ca8 + 856de6c commit ef9e6b5
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2024-03-18 15:24

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("kontres", "0005_bookableitem_allows_alcohol_and_more"),
]

operations = [
migrations.RenameField(
model_name="reservation",
old_name="alcohol_agreement",
new_name="serves_alcohol",
),
]
2 changes: 1 addition & 1 deletion app/kontres/models/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Reservation(BaseModel, BasePermissionModel):
null=True,
blank=True,
)
alcohol_agreement = models.BooleanField(default=False)
serves_alcohol = models.BooleanField(default=False)
sober_watch = models.ForeignKey(
User,
on_delete=models.SET_NULL,
Expand Down
35 changes: 18 additions & 17 deletions app/kontres/serializer/reservation_seralizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class ReservationSerializer(serializers.ModelSerializer):
)
author_detail = UserSerializer(source="author", read_only=True)

sober_watch = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), write_only=True, required=False
)
sober_watch_detail = UserSerializer(source="sober_watch", read_only=True)

class Meta:
model = Reservation
fields = "__all__"
Expand All @@ -56,23 +61,20 @@ def validate(self, data):
return data

def validate_alcohol(self, data):
if not data.get(
"alcohol_agreement",
self.instance.alcohol_agreement if self.instance else False,
):
raise serializers.ValidationError(
"Du må godta at dere vil følge reglene for alkoholbruk."
)
sober_watch = data.get(
"sober_watch", self.instance.sober_watch if self.instance else None
)
if (
not sober_watch
or not User.objects.filter(user_id=sober_watch.user_id).exists()
if data.get(
"serves_alcohol",
self.instance.serves_alcohol if self.instance else False,
):
raise serializers.ValidationError(
"Du må velge en edruvakt for reservasjonen."
sober_watch = data.get(
"sober_watch", self.instance.sober_watch if self.instance else None
)
if (
not sober_watch
or not User.objects.filter(user_id=sober_watch.user_id).exists()
):
raise serializers.ValidationError(
"Du må velge en edruvakt for reservasjonen."
)

def validate_group(self, value):
user = self.context["request"].user
Expand Down Expand Up @@ -104,7 +106,6 @@ def validate_state_change(self, data, user):
"state": "Du har ikke rettigheter til å endre reservasjonsstatusen."
}
)
pass

def validate_time_and_overlapping(self, data):

Expand Down Expand Up @@ -140,6 +141,7 @@ def validate_time_and_overlapping(self, data):
bookable_item=bookable_item,
end_time__gt=start_time,
start_time__lt=end_time,
state=ReservationStateEnum.CONFIRMED,
)
# Exclude the current instance if updating
if self.instance:
Expand All @@ -149,4 +151,3 @@ def validate_time_and_overlapping(self, data):
raise serializers.ValidationError(
"Det er en reservasjonsoverlapp for det gitte tidsrommet."
)
pass
9 changes: 9 additions & 0 deletions app/kontres/views/bookable_item.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from rest_framework import status
from rest_framework.response import Response

from app.common.permissions import BasicViewPermission
from app.common.viewsets import BaseViewSet
from app.kontres.models.bookable_item import BookableItem
Expand All @@ -10,3 +13,9 @@ class BookableItemViewSet(BaseViewSet):
queryset = BookableItem.objects.all()
serializer_class = BookableItemSerializer
permission_classes = [BasicViewPermission]

def destroy(self, request, *args, **kwargs):
super().destroy(self, request, *args, **kwargs)
return Response(
{"detail": "Gjenstanden ble slettet."}, status=status.HTTP_204_NO_CONTENT
)
4 changes: 3 additions & 1 deletion app/kontres/views/reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,6 @@ def update(self, request, *args, **kwargs):

def destroy(self, request, *args, **kwargs):
super().destroy(self, request, *args, **kwargs)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(
{"detail": "Reservasjonen ble slettet."}, status=status.HTTP_204_NO_CONTENT
)
12 changes: 12 additions & 0 deletions app/tests/kontres/test_bookable_item_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_admin_can_delete_bookable_item(admin_user, bookable_item):
f"/kontres/bookable_items/{bookable_item.id}/", format="json"
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.data["detail"] == "Gjenstanden ble slettet."


@pytest.mark.django_db
Expand Down Expand Up @@ -78,3 +79,14 @@ def test_admin_can_edit_bookable_item(admin_user, bookable_item):
)
assert response.status_code == status.HTTP_200_OK
assert response.data["name"] == "test"


@pytest.mark.django_db
def test_get_returns_empty_list_when_no_bookable_items(member):
client = get_api_client(user=member)
response = client.get("/kontres/bookable_items/", format="json")

assert response.status_code == status.HTTP_200_OK
assert (
response.data == []
), "Expected an empty list when there are no bookable items"
146 changes: 112 additions & 34 deletions app/tests/kontres/test_reservation_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def test_member_can_create_reservation(member, bookable_item):


@pytest.mark.django_db
def test_member_can_create_reservation_with_alcohol_agreement(member, bookable_item):
def test_member_can_create_reservation_with_alcohol_and_sober_watch(
member, bookable_item
):
client = get_api_client(user=member)

bookable_item.allows_alcohol = True
Expand All @@ -49,42 +51,15 @@ def test_member_can_create_reservation_with_alcohol_agreement(member, bookable_i
"bookable_item": bookable_item.id,
"start_time": "2030-10-10T10:00:00Z",
"end_time": "2030-10-10T11:00:00Z",
"alcohol_agreement": True,
"serves_alcohol": True,
"sober_watch": member.user_id,
},
format="json",
)

assert response.status_code == 201, response.data
assert response.data.get("alcohol_agreement") is True
assert response.data.get("sober_watch") == str(member.user_id)


@pytest.mark.django_db
def test_reservation_creation_fails_without_alcohol_agreement(member, bookable_item):
client = get_api_client(user=member)

bookable_item.allows_alcohol = True
bookable_item.save()

response = client.post(
"/kontres/reservations/",
{
"bookable_item": bookable_item.id,
"start_time": "2030-10-10T10:00:00Z",
"end_time": "2030-10-10T11:00:00Z",
# Notice the absence of "alcohol_agreement": True,
"sober_watch": member.user_id,
},
format="json",
)

assert response.status_code == 400
expected_error_message = "Du må godta at dere vil følge reglene for alkoholbruk."
actual_error_messages = response.data.get("non_field_errors", [])
assert any(
expected_error_message in error for error in actual_error_messages
), f"Expected specific alcohol agreement validation error: {expected_error_message}"
assert response.data.get("serves_alcohol") is True
assert response.data["sober_watch_detail"]["user_id"] == str(member.user_id)


@pytest.mark.django_db
Expand All @@ -100,14 +75,15 @@ def test_reservation_creation_fails_without_sober_watch(member, bookable_item):
"bookable_item": bookable_item.id,
"start_time": "2030-10-10T10:00:00Z",
"end_time": "2030-10-10T11:00:00Z",
"alcohol_agreement": True,
"serves_alcohol": True,
# Notice the absence of "sober_watch",
},
format="json",
)

assert response.status_code == 400
expected_error_message = "Du må velge en edruvakt for reservasjonen."
print(response.data)
expected_error_message = "Du må velge en edruvakt for reservasjonen."
actual_error_messages = response.data.get("non_field_errors", [])
assert any(
expected_error_message in error for error in actual_error_messages
Expand Down Expand Up @@ -186,6 +162,7 @@ def test_member_deleting_own_reservation(member, reservation):
client = get_api_client(user=member)
response = client.delete(f"/kontres/reservations/{reservation.id}/", format="json")
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.data["detail"] == "Reservasjonen ble slettet."


@pytest.mark.django_db
Expand Down Expand Up @@ -501,7 +478,9 @@ def test_unauthenticated_request_cannot_create_reservation(bookable_item):


@pytest.mark.django_db
def test_creating_overlapping_reservation(member, bookable_item, admin_user):
def test_creating_overlapping_reservation_should_not_work_when_confirmed(
member, bookable_item, admin_user
):
# Create a confirmed reservation using the ReservationFactory
existing_confirmed_reservation = ReservationFactory(
bookable_item=bookable_item,
Expand Down Expand Up @@ -532,6 +511,105 @@ def test_creating_overlapping_reservation(member, bookable_item, admin_user):
assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_updating_to_overlapping_reservation_should_not_work_when_confirmed(
member, bookable_item, admin_user
):
# Create two initial reservations, one confirmed and one pending
confirmed_reservation = ReservationFactory(
bookable_item=bookable_item,
start_time=timezone.now() + timezone.timedelta(hours=1),
end_time=timezone.now() + timezone.timedelta(hours=2),
state=ReservationStateEnum.CONFIRMED,
)

pending_reservation = ReservationFactory(
bookable_item=bookable_item,
start_time=confirmed_reservation.start_time
+ timezone.timedelta(minutes=30), # Overlapping time
end_time=confirmed_reservation.end_time + timezone.timedelta(minutes=30),
state=ReservationStateEnum.PENDING,
)

# Now attempt to update the pending reservation to confirmed, which should overlap with the confirmed_reservation
client = get_api_client(user=member)
response = client.put(
f"/kontres/reservations/{pending_reservation.id}/",
{"state": ReservationStateEnum.CONFIRMED},
format="json",
)

# The system should not allow this, as it would overlap with another confirmed reservation
assert (
response.status_code == status.HTTP_400_BAD_REQUEST
), "Should not update reservation to confirmed due to overlap"


@pytest.mark.django_db
def test_creating_overlapping_reservation_should_work_when_cancelled(
member, bookable_item, admin_user
):
existing_confirmed_reservation = ReservationFactory(
bookable_item=bookable_item,
start_time=timezone.now() + timezone.timedelta(hours=1),
end_time=timezone.now() + timezone.timedelta(hours=2),
state=ReservationStateEnum.CANCELLED, # Set the reservation as declined
)

# Now attempt to create an overlapping reservation
client = get_api_client(user=member)
overlapping_start_time = (
existing_confirmed_reservation.start_time + timezone.timedelta(minutes=30)
)
response = client.post(
"/kontres/reservations/",
{
"author": member.user_id,
"bookable_item": bookable_item.id,
"start_time": overlapping_start_time,
"end_time": existing_confirmed_reservation.end_time
+ timezone.timedelta(hours=1),
"state": ReservationStateEnum.PENDING,
},
format="json",
)

assert response.status_code == status.HTTP_201_CREATED


@pytest.mark.django_db
def test_creating_overlapping_reservation_should_work_when_pending(
member, bookable_item, admin_user
):
# Create a confirmed reservation using the ReservationFactory
existing_confirmed_reservation = ReservationFactory(
bookable_item=bookable_item,
start_time=timezone.now() + timezone.timedelta(hours=1),
end_time=timezone.now() + timezone.timedelta(hours=2),
state=ReservationStateEnum.PENDING, # Set the reservation as declined
)

# Now attempt to create an overlapping reservation
client = get_api_client(user=member)
overlapping_start_time = (
existing_confirmed_reservation.start_time + timezone.timedelta(minutes=30)
)
response = client.post(
"/kontres/reservations/",
{
"author": member.user_id,
"bookable_item": bookable_item.id,
"start_time": overlapping_start_time,
"end_time": existing_confirmed_reservation.end_time
+ timezone.timedelta(hours=1),
"state": ReservationStateEnum.PENDING,
},
format="json",
)

assert response.status_code == status.HTTP_201_CREATED


@pytest.mark.django_db
def test_retrieve_specific_reservation_within_its_date_range(member, bookable_item):
client = get_api_client(user=member)
Expand Down

0 comments on commit ef9e6b5

Please sign in to comment.