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
« 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
10TEST_PASSWORD = settings.TEST_USER_PASSWORD
12TEST_PASSWORD = "pass" # noqa: S105
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)
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 )
30 client = APIClient()
31 client.force_authenticate(user=operator_user)
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 )
40 assert response.status_code == status.HTTP_200_OK
42 booking.refresh_from_db()
44 expected_reason_part = f"Cancelled by Operator ({operator_user.username})"
46 assert booking.cancellation_reason.startswith(expected_reason_part)
47 assert "cleaning" in booking.cancellation_reason
48 assert booking.status == 'cancelled'
50@pytest.mark.django_db
51def test_operator_can_patch_own_spot():
52 client = APIClient()
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)
59 client.force_authenticate(user=operator_user)
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()
65 assert response.status_code == status.HTTP_200_OK
66 assert spot.is_ev is True
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)
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)
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"]
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"
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"
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
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)
120 api_client.force_authenticate(user)
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()
126 assert response.status_code == status.HTTP_400_BAD_REQUEST
127 assert spot.number == "A1"
129@pytest.mark.django_db
130def test_operator_cannot_patch_spot_from_other_lot():
131 client = APIClient()
133 lot1 = ParkingLot.objects.create(name="Lot 1", city="Kyiv", street="Main")
134 lot2 = ParkingLot.objects.create(name="Lot 2", city="Kyiv", street="Side")
136 operator = User.objects.create_user(username="op1")
137 OperatorProfile.objects.create(user=operator, lot=lot1)
139 spot_other_lot = Spot.objects.create(number="B2", lot=lot2, is_ev=False, is_disabled=False)
141 client.force_authenticate(operator)
142 url = f"/api/v1/lots/{lot2.id}/spots/{spot_other_lot.id}/operator-update/"
144 resp = client.patch(url, {"is_ev": True}, format="json")
145 spot_other_lot.refresh_from_db()
147 assert resp.status_code == status.HTTP_403_FORBIDDEN
148 assert spot_other_lot.is_ev is False
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
163 assert response.status_code == 200, response.data
164 assert elapsed < 3
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
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
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)
193 client.force_authenticate(user=user)
195 url = f"/api/v1/lots/{lot.id}/spots/{spot.id}/operator-update/"
197 response = client.patch(url, {"is_ev": True}, format="json")
199 assert response.status_code == 200
201@pytest.mark.django_db
202def test_operator_cannot_update_other_lot_spot():
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)
211 client.force_authenticate(user=op1)
213 url = f"/api/v1/lots/{lot2.id}/spots/{spot2.id}/operator-update/"
214 response = client.patch(url, {"is_ev": True}, format="json")
216 assert response.status_code == 403