Coverage for src/api/tests/test_permissions.py: 100%
103 statements
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-08 10:41 +0000
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-08 10:41 +0000
1import pytest
2from django.contrib.auth.models import User
3from rest_framework.test import APIRequestFactory
4from src.api.models import ParkingLot, Spot, Booking, OperatorProfile
5from src.api.permissions import IsLotOperator
6from django.utils import timezone
7from django.contrib.auth.models import AnonymousUser
8from rest_framework.test import APIClient
9from rest_framework import status
10factory = APIRequestFactory()
11from datetime import timedelta
12from unittest.mock import Mock
13from django.conf import settings
15TEST_PASSWORD = settings.TEST_USER_PASSWORD
17@pytest.mark.django_db
18def test_permission_allows_own_lot():
19 user = User.objects.create_user(username="op")
20 lot = ParkingLot.objects.create(name="Lot", city="Kyiv", street="Main")
21 OperatorProfile.objects.create(user=user, lot=lot)
23 request = factory.get("/")
24 request.user = user
25 view = Mock()
26 view.kwargs = {'lot_pk': lot.id}
28 permission = IsLotOperator()
29 assert permission.has_permission(request, view) is True
31@pytest.mark.django_db
32def test_permission_denies_other_lot():
33 user = User.objects.create_user(username="op")
34 lot1 = ParkingLot.objects.create(name="Lot1", city="Kyiv", street="Main")
35 lot2 = ParkingLot.objects.create(name="Lot2", city="Lviv", street="Other")
36 OperatorProfile.objects.create(user=user, lot=lot1)
38 request = factory.get("/")
39 request.user = user
40 view = Mock()
41 view.kwargs = {'lot_pk': lot2.id}
43 permission = IsLotOperator()
44 assert permission.has_permission(request, view) is False
47@pytest.mark.django_db
48def test_has_permission_for_non_operator():
49 user = User.objects.create_user(username="no_op")
50 request = factory.get("/")
51 request.user = user
52 permission = IsLotOperator()
53 assert permission.has_permission(request, view=None) is False
56@pytest.mark.django_db
57def test_object_permission_same_lot():
58 user = User.objects.create_user(username="op")
59 lot = ParkingLot.objects.create(name="Lot", city="Kyiv", street="Main")
60 OperatorProfile.objects.create(user=user, lot=lot)
61 spot = Spot.objects.create(number="A1", lot=lot)
62 booking = Booking.objects.create(
63 spot=spot,
64 start_at=timezone.now(),
65 end_at=timezone.now() + timezone.timedelta(hours=1)
66 )
68 request = factory.get("/")
69 request.user = user
70 perm = IsLotOperator()
71 assert perm.has_object_permission(request, view=None, obj=booking) is True
74@pytest.mark.django_db
75def test_object_permission_different_lot_denied():
76 user = User.objects.create_user(username="op2")
77 lot1 = ParkingLot.objects.create(name="Lot1", city="Kyiv", street="Main")
78 lot2 = ParkingLot.objects.create(name="Lot2", city="Lviv", street="Street")
79 OperatorProfile.objects.create(user=user, lot=lot1)
80 spot = Spot.objects.create(number="B1", lot=lot2)
81 booking = Booking.objects.create(
82 spot=spot,
83 start_at=timezone.now(),
84 end_at=timezone.now() + timezone.timedelta(hours=1)
85 )
87 request = factory.get("/")
88 request.user = user
89 perm = IsLotOperator()
90 assert perm.has_object_permission(request, view=None, obj=booking) is False
92def test_permission_denied_for_anonymous():
93 request = APIRequestFactory().get("/")
94 request.user = AnonymousUser()
95 perm = IsLotOperator()
96 assert not perm.has_permission(request, view=None)
98@pytest.mark.django_db
99def test_operator_cannot_cancel_already_cancelled_booking():
100 operator_user = User.objects.create_user(username="op_cancel_check")
101 lot = ParkingLot.objects.create(name="LotB", city="Kyiv", street="Main")
102 OperatorProfile.objects.create(user=operator_user, lot=lot)
103 spot = Spot.objects.create(number="B1", lot=lot)
105 booking = Booking.objects.create(
106 spot=spot,
107 start_at=timezone.now() + timezone.timedelta(hours=1),
108 end_at=timezone.now() + timezone.timedelta(hours=2),
109 status='cancelled',
110 cancellation_reason='Client changed plans.'
111 )
113 client = APIClient()
114 client.force_authenticate(user=operator_user)
116 cancel_url = f'/api/v1/bookings/{booking.id}/cancel-operator/'
117 response = client.post(
118 cancel_url,
119 {"reason": "Attempting to cancel an already cancelled booking."},
120 format='json'
121 )
122 assert response.status_code == status.HTTP_400_BAD_REQUEST
123 assert 'already cancelled' in response.data['detail'].lower()
125 booking.refresh_from_db()
126 assert booking.status == 'cancelled'
128@pytest.mark.django_db
129def test_operator_cannot_cancel_past_booking():
130 operator_user = User.objects.create_user(username="op_past_cancel")
131 lot = ParkingLot.objects.create(name="LotC", city="Kyiv", street="Main")
132 OperatorProfile.objects.create(user=operator_user, lot=lot)
133 spot = Spot.objects.create(number="C1", lot=lot)
135 now = timezone.now()
136 booking = Booking.objects.create(
137 spot=spot,
138 start_at=now - timedelta(hours=6),
139 end_at=now - timedelta(hours=5),
140 status='confirmed',
141 )
143 client = APIClient()
144 client.force_authenticate(user=operator_user)
146 cancel_url = f'/api/v1/bookings/{booking.id}/cancel-operator/'
147 response = client.post(
148 cancel_url,
149 {"reason": "Attempting to cancel a completed booking."},
150 format='json'
151 )
153 assert response.status_code == status.HTTP_400_BAD_REQUEST
154 assert 'already completed' in response.data['detail'].lower()
156 booking.refresh_from_db()
157 assert booking.status == 'confirmed', "Status should remain 'confirmed' after failed cancellation."