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

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 

14 

15TEST_PASSWORD = settings.TEST_USER_PASSWORD 

16 

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) 

22 

23 request = factory.get("/") 

24 request.user = user 

25 view = Mock() 

26 view.kwargs = {'lot_pk': lot.id} 

27 

28 permission = IsLotOperator() 

29 assert permission.has_permission(request, view) is True 

30 

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) 

37 

38 request = factory.get("/") 

39 request.user = user 

40 view = Mock() 

41 view.kwargs = {'lot_pk': lot2.id} 

42 

43 permission = IsLotOperator() 

44 assert permission.has_permission(request, view) is False 

45 

46 

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 

54 

55 

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 ) 

67 

68 request = factory.get("/") 

69 request.user = user 

70 perm = IsLotOperator() 

71 assert perm.has_object_permission(request, view=None, obj=booking) is True 

72 

73 

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 ) 

86 

87 request = factory.get("/") 

88 request.user = user 

89 perm = IsLotOperator() 

90 assert perm.has_object_permission(request, view=None, obj=booking) is False 

91 

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) 

97 

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) 

104 

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 ) 

112 

113 client = APIClient() 

114 client.force_authenticate(user=operator_user) 

115 

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() 

124 

125 booking.refresh_from_db() 

126 assert booking.status == 'cancelled' 

127 

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) 

134 

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 ) 

142 

143 client = APIClient() 

144 client.force_authenticate(user=operator_user) 

145 

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 ) 

152 

153 assert response.status_code == status.HTTP_400_BAD_REQUEST 

154 assert 'already completed' in response.data['detail'].lower() 

155 

156 booking.refresh_from_db() 

157 assert booking.status == 'confirmed', "Status should remain 'confirmed' after failed cancellation."