Coverage for src/api/tests/test_views.py: 100%

156 statements  

« prev     ^ index     » next       coverage.py v7.11.1, created at 2025-11-08 10:41 +0000

1import pytest 

2from rest_framework.test import APIClient 

3from rest_framework import status 

4from django.contrib.auth.models import User 

5from src.api.models import ParkingLot, Spot, Booking, OperatorProfile 

6from django.utils import timezone 

7import time 

8from django.conf import settings 

9 

10TEST_PASSWORD = settings.TEST_USER_PASSWORD 

11 

12TEST_PASSWORD = "pass" # noqa: S105 

13 

14@pytest.mark.django_db 

15def test_operator_cancels_booking_and_reason_is_detailed(): 

16 operator_user = User.objects.create_user(username="op_test") 

17 lot = ParkingLot.objects.create(name="LotA", city="Kyiv", street="Main") 

18 OperatorProfile.objects.create(user=operator_user, lot=lot) 

19 spot = Spot.objects.create(number="A1", lot=lot) 

20 

21 other_user = User.objects.create_user(username="client_user") 

22 booking = Booking.objects.create( 

23 user=other_user, 

24 spot=spot, 

25 start_at=timezone.now() + timezone.timedelta(hours=1), 

26 end_at=timezone.now() + timezone.timedelta(hours=2), 

27 status='confirmed' 

28 ) 

29 

30 client = APIClient() 

31 client.force_authenticate(user=operator_user) 

32 

33 cancel_url = f'/api/v1/bookings/{booking.id}/cancel-operator/' 

34 response = client.post( 

35 cancel_url, 

36 {"reason": "Vehicle must be moved for cleaning."}, 

37 format='json' 

38 ) 

39 

40 assert response.status_code == status.HTTP_200_OK 

41 

42 booking.refresh_from_db() 

43 

44 expected_reason_part = f"Cancelled by Operator ({operator_user.username})" 

45 

46 assert booking.cancellation_reason.startswith(expected_reason_part) 

47 assert "cleaning" in booking.cancellation_reason 

48 assert booking.status == 'cancelled' 

49 

50@pytest.mark.django_db 

51def test_operator_can_patch_own_spot(): 

52 client = APIClient() 

53 

54 operator_user = User.objects.create_user(username="op") 

55 lot = ParkingLot.objects.create(name="Mall", city="Kyiv", street="Main") 

56 OperatorProfile.objects.create(user=operator_user, lot=lot) 

57 spot = Spot.objects.create(number="A1", lot=lot, is_ev=False, is_disabled=False) 

58 

59 client.force_authenticate(user=operator_user) 

60 

61 url = f"/api/v1/lots/{lot.id}/spots/{spot.id}/operator-update/" 

62 response = client.patch(url, {"is_ev": True}, format="json") 

63 spot.refresh_from_db() 

64 

65 assert response.status_code == status.HTTP_200_OK 

66 assert spot.is_ev is True 

67 

68@pytest.mark.django_db 

69def test_parking_lot_list_and_detail(): 

70 lot = ParkingLot.objects.create(name="Sky", city="Kyiv", street="Main") 

71 Spot.objects.create(number="A1", lot=lot) 

72 client = APIClient() 

73 resp_list = client.get("/api/v1/lots/") 

74 assert resp_list.status_code == 200 

75 resp_detail = client.get(f"/api/v1/lots/{lot.id}/") 

76 assert resp_detail.status_code == 200 

77 assert "Sky" in str(resp_detail.data) 

78 

79@pytest.mark.django_db 

80def test_create_and_cancel_booking(): 

81 user = User.objects.create_user("user") 

82 client = APIClient() 

83 client.force_authenticate(user) 

84 lot = ParkingLot.objects.create(name="Test", city="Kyiv", street="Main") 

85 spot = Spot.objects.create(number="A1", lot=lot) 

86 start = timezone.now() + timezone.timedelta(hours=1) 

87 end = start + timezone.timedelta(hours=1) 

88 

89 resp = client.post("/api/v1/bookings/create/", {"spot": spot.id, "start_at": start, "end_at": end}, format="json") 

90 assert resp.status_code == 201 

91 booking_id = resp.data["id"] 

92 

93 cancel = client.post(f"/api/v1/bookings/{booking_id}/cancel/", {"reason": "no longer needed"}, format="json") 

94 assert cancel.status_code == 200 

95 assert cancel.data["status"] == "cancelled" 

96 

97@pytest.mark.django_db 

98def test_user_register_and_update_me(): 

99 client = APIClient() 

100 reg = client.post("/api/v1/users/register/", {"username": "newu", "email": "n@e.com", "password": settings.STRONG_PASSWORD_FOR_TESTS}, format="json") 

101 assert reg.status_code == 201 

102 user = User.objects.get(username="newu") 

103 client.force_authenticate(user) 

104 patch = client.patch("/api/v1/users/me/", {"first_name": "Valya"}, format="json") 

105 assert patch.status_code == 200 

106 assert patch.data["first_name"] == "Valya" 

107 

108@pytest.mark.django_db 

109def test_operator_cannot_change_spot_number(): 

110 api_client=APIClient() 

111 from django.contrib.auth.models import User 

112 from src.api.models import ParkingLot, Spot, OperatorProfile 

113 from rest_framework import status 

114 

115 user = User.objects.create_user(username="operator") 

116 lot = ParkingLot.objects.create(name="Mall", city="Kyiv", street="Main") 

117 OperatorProfile.objects.create(user=user, lot=lot) 

118 spot = Spot.objects.create(number="A1", lot=lot, is_ev=False, is_disabled=False) 

119 

120 api_client.force_authenticate(user) 

121 

122 url = f"/api/v1/lots/{lot.id}/spots/{spot.id}/operator-update/" 

123 response = api_client.patch(url, {"number": "Z9"}, format="json") 

124 spot.refresh_from_db() 

125 

126 assert response.status_code == status.HTTP_400_BAD_REQUEST 

127 assert spot.number == "A1" 

128 

129@pytest.mark.django_db 

130def test_operator_cannot_patch_spot_from_other_lot(): 

131 client = APIClient() 

132 

133 lot1 = ParkingLot.objects.create(name="Lot 1", city="Kyiv", street="Main") 

134 lot2 = ParkingLot.objects.create(name="Lot 2", city="Kyiv", street="Side") 

135 

136 operator = User.objects.create_user(username="op1") 

137 OperatorProfile.objects.create(user=operator, lot=lot1) 

138 

139 spot_other_lot = Spot.objects.create(number="B2", lot=lot2, is_ev=False, is_disabled=False) 

140 

141 client.force_authenticate(operator) 

142 url = f"/api/v1/lots/{lot2.id}/spots/{spot_other_lot.id}/operator-update/" 

143 

144 resp = client.patch(url, {"is_ev": True}, format="json") 

145 spot_other_lot.refresh_from_db() 

146 

147 assert resp.status_code == status.HTTP_403_FORBIDDEN 

148 assert spot_other_lot.is_ev is False 

149 

150def test_list_spots_performance(db): 

151 client = APIClient() 

152 lot = ParkingLot.objects.create( 

153 name="Test Lot", 

154 building="Main Tower", 

155 city="Kyiv" 

156 ) 

157 Spot.objects.create(lot=lot, number="A1") 

158 url = f"/api/v1/lots/{lot.id}/spots/" 

159 start = time.perf_counter() 

160 response = client.get(url) 

161 elapsed = time.perf_counter() - start 

162 

163 assert response.status_code == 200, response.data 

164 assert elapsed < 3 

165 

166@pytest.mark.django_db 

167def test_delete_nonexistent_spot_returns_404(): 

168 client = APIClient() 

169 admin = User.objects.create_user(username="admin", is_staff=True) 

170 client.force_authenticate(admin) 

171 resp = client.delete("/api/v1/lots/999/spots/999/") 

172 assert resp.status_code == 404 

173 

174@pytest.mark.django_db 

175def test_spot_list_loads_under_half_second(): 

176 lot = ParkingLot.objects.create(name="PerfLot", city="Kyiv", street="Main") 

177 Spot.objects.bulk_create([Spot(number=f"S{i}", lot=lot) for i in range(1000)]) 

178 client = APIClient() 

179 start = time.perf_counter() 

180 resp = client.get(f"/api/v1/lots/{lot.id}/spots/") 

181 elapsed = time.perf_counter() - start 

182 assert resp.status_code == 200 

183 assert elapsed < 0.5 

184 

185@pytest.mark.django_db 

186def test_operator_can_update_own_spot(): 

187 client = APIClient() 

188 user = User.objects.create_user(username="op") 

189 lot = ParkingLot.objects.create(name="Lot", city="Kyiv", street="Main") 

190 OperatorProfile.objects.create(user=user, lot=lot) 

191 spot = Spot.objects.create(number="A1", lot=lot) 

192 

193 client.force_authenticate(user=user) 

194 

195 url = f"/api/v1/lots/{lot.id}/spots/{spot.id}/operator-update/" 

196 

197 response = client.patch(url, {"is_ev": True}, format="json") 

198 

199 assert response.status_code == 200 

200 

201@pytest.mark.django_db 

202def test_operator_cannot_update_other_lot_spot(): 

203 

204 client = APIClient() 

205 op1 = User.objects.create_user(username="op1") 

206 lot1 = ParkingLot.objects.create(name="Lot1", city="Kyiv", street="Main") 

207 lot2 = ParkingLot.objects.create(name="Lot2", city="Lviv", street="Other") 

208 OperatorProfile.objects.create(user=op1, lot=lot1) 

209 spot2 = Spot.objects.create(number="B1", lot=lot2) 

210 

211 client.force_authenticate(user=op1) 

212 

213 url = f"/api/v1/lots/{lot2.id}/spots/{spot2.id}/operator-update/" 

214 response = client.patch(url, {"is_ev": True}, format="json") 

215 

216 assert response.status_code == 403