• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gcivil-nyu-org / team4-wed-spring25 / 545

17 Apr 2025 06:12PM UTC coverage: 93.377% (+0.4%) from 92.942%
545

push

travis-pro

web-flow
Merge pull request #265 from gcivil-nyu-org/ka-message-feature

Fixing black

1424 of 1525 relevant lines covered (93.38%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.03
/parks/tests.py
1
from django.test import TestCase, Client
1✔
2
from django.urls import reverse
1✔
3
from django.contrib.auth.models import User
1✔
4
from .models import DogRunNew, Review, ParkImage, ReviewReport, ImageReport, Reply
1✔
5
from parks.templatetags.display_rating import render_stars
1✔
6
from parks.templatetags import image_filters
1✔
7
from django.utils.text import slugify
1✔
8
from django.core import mail
1✔
9
from django.contrib.messages import get_messages
1✔
10

11
from django.utils import timezone
1✔
12
from datetime import timedelta
1✔
13
from parks.models import ParkPresence
1✔
14

15
from unittest.mock import patch
1✔
16
from django.core.files.uploadedfile import SimpleUploadedFile
1✔
17

18
from .models import Message
1✔
19

20
from cloudinary import config as cloudinary_config
1✔
21

22

23
@patch(
1✔
24
    "cloudinary.uploader.upload",
25
    return_value={
26
        "asset_id": "dummy_asset_id",
27
        "public_id": "dummy_id",
28
        "version": "1234567890",
29
        "signature": "dummy_signature",
30
        "width": 800,
31
        "height": 600,
32
        "format": "jpg",
33
        "resource_type": "image",
34
        "type": "upload",
35
        "secure_url": "https://res.cloudinary.com/demo/image/upload/sample.jpg",
36
        "url": "https://res.cloudinary.com/demo/image/upload/sample.jpg",
37
    },
38
)
39
class ErrorPageTests(TestCase):
1✔
40
    def test_trigger_400(self, _mock=None):
1✔
41
        response = self.client.get("/test400/")
1✔
42
        self.assertEqual(response.status_code, 400)
1✔
43
        self.assertTemplateUsed(response, "400.html")
1✔
44

45
    def test_trigger_403(self, _mock=None):
1✔
46
        response = self.client.get("/test403/")
1✔
47
        self.assertEqual(response.status_code, 403)
1✔
48
        self.assertTemplateUsed(response, "403.html")
1✔
49

50
    def test_trigger_404(self, _mock=None):
1✔
51
        response = self.client.get("/test404/")
1✔
52
        self.assertEqual(response.status_code, 404)
1✔
53
        self.assertTemplateUsed(response, "404.html")
1✔
54

55
    def test_trigger_500(self, _mock=None):
1✔
56
        response = self.client.get("/test500/")
1✔
57
        self.assertEqual(response.status_code, 500)
1✔
58
        self.assertTemplateUsed(response, "500.html")
1✔
59

60

61
class UniqueEmailTests(TestCase):
1✔
62
    def setUp(self):
1✔
63
        self.client = Client()
1✔
64
        User.objects.create_user(
1✔
65
            username="existinguser",
66
            email="duplicate@pawpark.com",
67
            password="SomeStrongPassword1",
68
        )
69
        self.register_url = reverse("register")
1✔
70

71
    def test_duplicate_email_registration(self):
1✔
72
        """
73
        Attempting to register a new user with an email that already exists should
74
        re-render the form with an error message.
75
        """
76
        response = self.client.post(
1✔
77
            self.register_url,
78
            {
79
                "username": "newuser",
80
                "email": "duplicate@pawpark.com",
81
                "password1": "StrongPass123",
82
                "password2": "StrongPass123",
83
                "role": "user",
84
                "admin_access_code": "",
85
            },
86
        )
87
        self.assertEqual(response.status_code, 200)
1✔
88
        self.assertContains(response, "A user with that email address already exists.")
1✔
89
        self.assertFalse(User.objects.filter(username="newuser").exists())
1✔
90

91

92
class WeakPasswordTests(TestCase):
1✔
93
    def setUp(self):
1✔
94
        self.client = Client()
1✔
95
        self.register_url = reverse("register")
1✔
96

97
    def test_too_short_password(self):
1✔
98
        response = self.client.post(
1✔
99
            self.register_url,
100
            {
101
                "username": "weakuser",
102
                "password1": "123",
103
                "password2": "123",
104
                "role": "user",
105
            },
106
        )
107
        self.assertEqual(response.status_code, 200)
1✔
108
        self.assertFalse(User.objects.filter(username="weakuser").exists())
1✔
109
        self.assertContains(response, "must contain at least 8 characters")
1✔
110

111
    def test_entirely_numeric_password(self):
1✔
112
        response = self.client.post(
1✔
113
            self.register_url,
114
            {
115
                "username": "numericuser",
116
                "password1": "12345678",
117
                "password2": "12345678",
118
                "role": "user",
119
            },
120
        )
121
        self.assertEqual(response.status_code, 200)
1✔
122
        self.assertFalse(User.objects.filter(username="numericuser").exists())
1✔
123
        self.assertContains(response, "can’t be entirely numeric")
1✔
124

125

126
class PasswordResetTests(TestCase):
1✔
127
    def setUp(self):
1✔
128
        self.client = Client()
1✔
129
        self.user = User.objects.create_user(
1✔
130
            "resetuser", "reset@pawpark.com", "Pass123456"
131
        )
132

133
    def test_password_reset_page_loads(self):
1✔
134
        url = reverse("password_reset")
1✔
135
        response = self.client.get(url)
1✔
136
        self.assertEqual(response.status_code, 200)
1✔
137
        self.assertTemplateUsed(response, "registration/password_reset_form.html")
1✔
138

139
    def test_password_reset_flow(self):
1✔
140
        """
141
        Ensure that posting an email to password_reset
142
        sends the user to password_reset_done,
143
        and optionally check that an email was
144
        "sent" (console backend or etc.)
145
        """
146
        url = reverse("password_reset")
1✔
147
        response = self.client.post(url, {"email": "reset@pawpark.com"})
1✔
148
        self.assertEqual(response.status_code, 302)
1✔
149
        self.assertRedirects(response, reverse("password_reset_done"))
1✔
150

151
        self.assertEqual(len(mail.outbox), 1)
1✔
152
        self.assertIn("resetuser", mail.outbox[0].body)
1✔
153

154

155
class AdminSignUpTests(TestCase):
1✔
156
    def setUp(self):
1✔
157
        self.client = Client()
1✔
158
        self.register_url = reverse("register")
1✔
159

160
    def test_admin_signup_with_correct_code(self):
1✔
161
        """
162
        Signing up as admin with correct access code should create a staff user.
163
        """
164
        response = self.client.post(
1✔
165
            self.register_url,
166
            {
167
                "username": "adminuser",
168
                "password1": "StrongAdminPass123",
169
                "password2": "StrongAdminPass123",
170
                "role": "admin",
171
                "admin_access_code": "SUPERDOG123",
172
            },
173
        )
174
        self.assertEqual(response.status_code, 302)
1✔
175
        self.assertTrue(User.objects.filter(username="adminuser").exists())
1✔
176
        user = User.objects.get(username="adminuser")
1✔
177
        self.assertTrue(user.is_staff)
1✔
178

179
    def test_admin_signup_with_wrong_code(self):
1✔
180
        """
181
        Signing up as admin with wrong code should fail and not create staff user.
182
        """
183
        response = self.client.post(
1✔
184
            self.register_url,
185
            {
186
                "username": "fakeadmin",
187
                "password1": "StrongAdminPass123",
188
                "password2": "StrongAdminPass123",
189
                "role": "admin",
190
                "admin_access_code": "WRONGCODE",
191
            },
192
        )
193
        self.assertEqual(response.status_code, 200)
1✔
194
        self.assertFalse(User.objects.filter(username="fakeadmin").exists())
1✔
195

196
    def test_signup_as_normal_user_ignores_access_code(self):
1✔
197
        """
198
        If someone chooses 'user' role, the admin_access_code is irrelevant.
199
        """
200
        response = self.client.post(
1✔
201
            self.register_url,
202
            {
203
                "username": "normaluser",
204
                "password1": "StrongPass456",
205
                "password2": "StrongPass456",
206
                "role": "user",
207
                "admin_access_code": "SUPERDOG123",
208
            },
209
        )
210
        self.assertEqual(response.status_code, 302)
1✔
211
        self.assertTrue(User.objects.filter(username="normaluser").exists())
1✔
212
        user = User.objects.get(username="normaluser")
1✔
213
        self.assertFalse(user.is_staff)
1✔
214

215

216
class LoginTests(TestCase):
1✔
217
    def setUp(self):
1✔
218
        self.client = Client()
1✔
219
        self.user = User.objects.create_user(
1✔
220
            username="testuser", password="StrongPass123"
221
        )
222

223
    def test_login_page_loads(self):
1✔
224
        """Ensure the login page loads properly."""
225
        response = self.client.get(reverse("login"))
1✔
226
        self.assertEqual(response.status_code, 200)
1✔
227
        self.assertTemplateUsed(response, "parks/login.html")
1✔
228

229
    def test_valid_login(self):
1✔
230
        """Ensure a valid user can log in."""
231
        response = self.client.post(
1✔
232
            reverse("login"), {"username": "testuser", "password": "StrongPass123"}
233
        )
234
        self.assertEqual(response.status_code, 302)
1✔
235

236

237
class AuthTests(TestCase):
1✔
238
    def setUp(self):
1✔
239
        self.client = Client()
1✔
240

241
    def test_register_page_loads(self):
1✔
242
        """Ensure the registration page loads properly."""
243
        response = self.client.get(reverse("register"))
1✔
244
        self.assertEqual(response.status_code, 200)
1✔
245
        self.assertTemplateUsed(response, "parks/register.html")
1✔
246

247
    def test_user_registration(self):
1✔
248
        """Ensure a new user can register successfully."""
249
        response = self.client.post(
1✔
250
            reverse("register"),
251
            {
252
                "username": "testuser",
253
                "password1": "StrongPass123",
254
                "password2": "StrongPass123",
255
                "role": "user",  # Ensure this field is required
256
            },
257
        )
258
        self.assertEqual(response.status_code, 302)
1✔
259
        self.assertTrue(User.objects.filter(username="testuser").exists())
1✔
260

261

262
class ParkModelTest(TestCase):
1✔
263
    def setUp(self):
1✔
264
        self.client = Client()
1✔
265
        self.park = DogRunNew.objects.create(
1✔
266
            id="1",
267
            prop_id="1234",
268
            name="Central Park",
269
            address="New York, NY",
270
            dogruns_type="Small",
271
            accessible="Yes",
272
            notes="Test park notes",
273
            google_name="Central Park",
274
            borough="M",
275
            zip_code="United States",
276
            formatted_address="Central Pk N, New York, NY, USA",
277
            latitude=40.7987768,
278
            longitude=-73.9537196,
279
            additional={
280
                "geometry": {
281
                    "bounds": {
282
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
283
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
284
                    },
285
                    "location": {"lat": 40.7987768, "lng": -73.9537196},
286
                    "location_type": "GEOMETRIC_CENTER",
287
                    "viewport": {
288
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
289
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
290
                    },
291
                }
292
            },
293
            display_name="Central Park",
294
            slug=slugify(f"{'Central Park'}-{'1234'}"),
295
        )
296

297
    def test_park_creation(self):
1✔
298
        self.assertEqual(self.park.name, "Central Park")
1✔
299
        self.assertEqual(self.park.address, "New York, NY")
1✔
300
        self.assertEqual(self.park.notes, "Test park notes")
1✔
301
        self.assertEqual(self.park.slug, "central-park-1234")
1✔
302

303

304
class ReviewModelTest(TestCase):
1✔
305
    def setUp(self):
1✔
306
        self.user = User.objects.create_user(username="testuser", password="123456abc")
1✔
307
        self.park = DogRunNew.objects.create(
1✔
308
            id="2",
309
            prop_id="5678",
310
            name="Brooklyn Park",
311
            address="Brooklyn, NY",
312
            dogruns_type="Large",
313
            accessible="No",
314
            notes="Another test park",
315
            display_name="Brooklyn Park",
316
            slug=slugify(f"{'Brooklyn Park'}-{'5678'}"),
317
        )
318
        self.review = Review.objects.create(
1✔
319
            park=self.park, text="Great park!", rating=5, user=self.user
320
        )
321

322
    def test_review_creation(self):
1✔
323
        self.assertEqual(self.review.text, "Great park!")
1✔
324
        self.assertEqual(self.review.rating, 5)
1✔
325
        self.assertEqual(self.review.park.name, "Brooklyn Park")
1✔
326

327
    def test_review_str_method(self):
1✔
328
        self.assertEqual(str(self.review), "Review for Brooklyn Park (5 stars)")
1✔
329

330

331
class CombinedViewTest(TestCase):
1✔
332
    def test_combined_view(self):
1✔
333
        response = self.client.get(reverse("park_and_map"))
1✔
334
        self.assertEqual(response.status_code, 200)
1✔
335

336
    def setUp(self):
1✔
337
        self.client = Client()
1✔
338
        # One park in Manhattan
339
        self.park_manhattan = DogRunNew.objects.create(
1✔
340
            id="1",
341
            prop_id="1234",
342
            name="Central Park",
343
            address="New York, NY",
344
            dogruns_type="Small",
345
            accessible="Yes",
346
            notes="Manhattan park",
347
            google_name="Central Park",
348
            borough="M",
349
            zip_code="10024",
350
            latitude=40.7987768,
351
            longitude=-73.9537196,
352
            display_name="Central Park",
353
        )
354
        # One park in Brooklyn
355
        self.park_brooklyn = DogRunNew.objects.create(
1✔
356
            id="2",
357
            prop_id="5678",
358
            name="Brooklyn Bridge Park",
359
            address="Brooklyn, NY",
360
            dogruns_type="Large",
361
            accessible="Yes",
362
            notes="Brooklyn park",
363
            google_name="Brooklyn Bridge Park",
364
            borough="B",
365
            zip_code="11201",
366
            latitude=40.700292,
367
            longitude=-73.996123,
368
            display_name="Brooklyn Bridge Park",
369
        )
370

371
    def test_combined_view_filters_by_borough(self):
1✔
372
        response = self.client.get(reverse("park_and_map"), {"borough": "M"})
1✔
373
        self.assertEqual(response.status_code, 200)
1✔
374
        self.assertContains(response, "Central Park")
1✔
375
        self.assertNotContains(response, "Brooklyn Bridge Park")
1✔
376

377

378
class ParkDetailViewTest(TestCase):
1✔
379
    def setUp(self):
1✔
380
        """Set up the test client and create a test park."""
381
        self.client = Client()
1✔
382

383
        self.park = DogRunNew.objects.create(
1✔
384
            id="1",
385
            prop_id="1234",
386
            name="Central Park",
387
            address="New York, NY",
388
            dogruns_type="Small",
389
            accessible="Yes",
390
            notes="Test park notes",
391
            google_name="Central Park",
392
            borough="M",
393
            zip_code="United States",
394
            formatted_address="Central Pk N, New York, NY, USA",
395
            latitude=40.7987768,
396
            longitude=-73.9537196,
397
            additional={
398
                "geometry": {
399
                    "bounds": {
400
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
401
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
402
                    },
403
                    "location": {"lat": 40.7987768, "lng": -73.9537196},
404
                    "location_type": "GEOMETRIC_CENTER",
405
                    "viewport": {
406
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
407
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
408
                    },
409
                }
410
            },
411
            display_name="Central Park",
412
            slug="central-park-1234",
413
        )
414

415
        self.park2 = DogRunNew.objects.create(
1✔
416
            id="2",
417
            prop_id="4321",
418
            name="Allison Pond Park",
419
            address="Staten Island",
420
            dogruns_type="Small",
421
            accessible="Yes",
422
            notes="Test park notes",
423
            google_name="Allison Pond Park",
424
            borough="Q",
425
            zip_code="United States",
426
            formatted_address="Allison Pond Park, Staten Island, NY 10301, USA",
427
            latitude=40.7987768,
428
            longitude=-73.9537196,
429
            display_name="Allison Pond Park",
430
            slug="allison-pond-park-4321",
431
        )
432

433
    def test_park_detail_page_loads(self):
1✔
434
        url = self.park.detail_page_url()
1✔
435
        response = self.client.get(url)
1✔
436
        self.assertEqual(response.status_code, 200)
1✔
437
        self.assertTemplateUsed(response, "parks/park_detail.html")
1✔
438
        self.assertContains(response, "Central Park")
1✔
439

440
    def test_redirect_on_wrong_slug(self):
1✔
441
        url = reverse("park_detail", kwargs={"slug": "wrong-slug", "id": self.park.id})
1✔
442
        response = self.client.get(url)
1✔
443
        self.assertEqual(response.status_code, 301)
1✔
444

445
        correct_response = url = reverse(
1✔
446
            "park_detail", kwargs={"slug": self.park.slug, "id": self.park.id}
447
        )
448

449
        self.assertRedirects(response, correct_response, status_code=301)
1✔
450

451
    def test_404_on_nonexistent_id(self):
1✔
452
        url = reverse("park_detail", kwargs={"slug": "central-park", "id": "-4"})
1✔
453
        response = self.client.get(url)
1✔
454
        self.assertEqual(response.status_code, 404)
1✔
455

456
    def test_redirect_on_wrong_id_right_slug(self):
1✔
457
        url = reverse(
1✔
458
            "park_detail", kwargs={"slug": "central-park", "id": self.park2.id}
459
        )
460
        response = self.client.get(url)
1✔
461

462
        self.assertEqual(response.status_code, 301)
1✔
463

464
        expected_url = reverse(
1✔
465
            "park_detail", kwargs={"slug": self.park2.slug, "id": self.park2.id}
466
        )
467
        self.assertRedirects(
1✔
468
            response, expected_url, status_code=301, target_status_code=200
469
        )
470

471
    from django.contrib.messages import get_messages
1✔
472

473

474
def test_submit_review_non_integer_rating(self):
1✔
475
    self.client.login(username="testuser", password="testpass")
×
476

477
    response = self.client.post(
×
478
        self.park.detail_page_url(),
479
        {
480
            "form_type": "submit_review",
481
            "text": "This should not go through.",
482
            "rating": "abc",
483
        },
484
        follow=True,
485
    )
486

487
    self.assertEqual(response.status_code, 200)
×
488

489
    messages = list(get_messages(response.wsgi_request))
×
490
    self.assertTrue(
×
491
        any("Please select a rating before submitting." in str(m) for m in messages),
492
        "Expected error message not found in messages.",
493
    )
494

495

496
class ReportFunctionalityTests(TestCase):
1✔
497
    def setUp(self):
1✔
498
        self.client = Client()
1✔
499
        self.user = User.objects.create_user(
1✔
500
            username="reporter", password="testpass123"
501
        )
502
        self.other_user = User.objects.create_user(
1✔
503
            username="uploader", password="testpass123"
504
        )
505

506
        self.park = DogRunNew.objects.create(
1✔
507
            id="10",
508
            prop_id="9999",
509
            name="Test Park",
510
            address="Test Address",
511
            dogruns_type="All",
512
            accessible="Yes",
513
            formatted_address="Test Address",
514
            latitude=40.0,
515
            longitude=-73.0,
516
        )
517

518
        self.image = ParkImage.objects.create(
1✔
519
            park=self.park,
520
            image="https://res.cloudinary.com/demo/image/upload/sample.jpg",
521
            user=self.other_user,
522
        )
523

524
        self.review = Review.objects.create(
1✔
525
            park=self.park, text="Nice place!", rating=4, user=self.other_user
526
        )
527

528
        self.client.login(username="reporter", password="testpass123")
1✔
529

530
    def test_report_image_creates_record(self):
1✔
531
        response = self.client.post(
1✔
532
            reverse("report_image", args=[self.image.id]),
533
            {"reason": "Inappropriate image"},
534
        )
535
        self.assertEqual(response.status_code, 302)
1✔
536
        self.assertEqual(self.image.reports.count(), 1)
1✔
537
        report = self.image.reports.first()
1✔
538
        self.assertEqual(report.reason, "Inappropriate image")
1✔
539
        self.assertEqual(report.user, self.user)
1✔
540

541
    def test_report_review_creates_record(self):
1✔
542
        response = self.client.post(
1✔
543
            reverse("park_detail", args=[self.park.slug, self.park.id]),
544
            {
545
                "form_type": "report_review",
546
                "review_id": self.review.id,
547
                "reason": "Offensive content",
548
            },
549
        )
550
        self.assertEqual(response.status_code, 302)
1✔
551
        self.assertEqual(self.review.reports.count(), 1)
1✔
552
        report = self.review.reports.first()
1✔
553
        self.assertEqual(report.reason, "Offensive content")
1✔
554
        self.assertEqual(report.reported_by, self.user)
1✔
555

556
    def test_submit_review(self):
1✔
557
        response = self.client.post(
1✔
558
            reverse("park_detail", args=[self.park.slug, self.park.id]),
559
            {"form_type": "submit_review", "text": "Another review!", "rating": "5"},
560
        )
561
        self.assertEqual(response.status_code, 302)
1✔
562
        self.assertEqual(Review.objects.filter(park=self.park).count(), 2)
1✔
563

564
    def test_review_report_str(self):
1✔
565
        report = ReviewReport.objects.create(
1✔
566
            review=self.review, reported_by=self.user, reason="Inappropriate content"
567
        )
568
        self.assertIn("Reported by", str(report))
1✔
569
        self.assertIn(str(self.review.id), str(report))
1✔
570

571
    def test_image_report_str(self):
1✔
572
        report = ImageReport.objects.create(
1✔
573
            image=self.image, user=self.user, reason="Offensive image"
574
        )
575
        self.assertIn("Report by", str(report))
1✔
576
        self.assertIn(str(self.image.id), str(report))
1✔
577

578
    def test_missing_reason_does_not_create_report(self):
1✔
579
        self.client.login(username="user2", password="testpass")
1✔
580
        response = self.client.post(
1✔
581
            reverse("report_image", args=[self.image.id]),
582
            {"reason": ""},
583
        )
584
        self.assertEqual(ImageReport.objects.count(), 0)
1✔
585
        self.assertEqual(response.status_code, 302)
1✔
586

587
    def test_duplicate_review_report(self):
1✔
588
        # First report
589
        response1 = self.client.post(
1✔
590
            reverse("park_detail", args=[self.park.slug, self.park.id]),
591
            {
592
                "form_type": "report_review",
593
                "review_id": self.review.id,
594
                "reason": "Spam",
595
            },
596
        )
597
        self.assertEqual(response1.status_code, 302)
1✔
598
        self.assertEqual(self.review.reports.count(), 1)
1✔
599

600
        # Second report by same user
601
        response2 = self.client.post(
1✔
602
            reverse("park_detail", args=[self.park.slug, self.park.id]),
603
            {
604
                "form_type": "report_review",
605
                "review_id": self.review.id,
606
                "reason": "Still spam",
607
            },
608
        )
609
        self.assertEqual(response2.status_code, 302)
1✔
610
        self.assertEqual(self.review.reports.count(), 1)  # should still be 1
1✔
611

612
    def test_duplicate_image_report(self):
1✔
613
        # First report
614
        response1 = self.client.post(
1✔
615
            reverse("report_image", args=[self.image.id]),
616
            {"reason": "Bad image"},
617
        )
618
        self.assertEqual(response1.status_code, 302)
1✔
619
        self.assertEqual(self.image.reports.count(), 1)
1✔
620

621
        # Second report by same user
622
        response2 = self.client.post(
1✔
623
            reverse("report_image", args=[self.image.id]),
624
            {"reason": "Still bad"},
625
        )
626
        self.assertEqual(response2.status_code, 302)
1✔
627
        self.assertEqual(self.image.reports.count(), 1)  # should still be 1
1✔
628

629

630
class DeleteTests(TestCase):
1✔
631
    def setUp(self):
1✔
632
        self.client = Client()
1✔
633
        self.user = User.objects.create_user(username="deleter", password="123pass")
1✔
634
        self.client.login(username="deleter", password="123pass")
1✔
635

636
        self.park = DogRunNew.objects.create(
1✔
637
            id="22",
638
            prop_id="9988",
639
            name="Del Park",
640
            address="Somewhere",
641
            dogruns_type="All",
642
            accessible="Yes",
643
            formatted_address="Addr",
644
            latitude=40.0,
645
            longitude=-73.0,
646
        )
647
        self.review = Review.objects.create(
1✔
648
            park=self.park, text="Review", rating=4, user=self.user
649
        )
650
        self.image = ParkImage.objects.create(
1✔
651
            park=self.park,
652
            image="https://res.cloudinary.com/demo/image/upload/sample.jpg",
653
            user=self.user,
654
        )
655

656
    def test_delete_review(self):
1✔
657
        response = self.client.post(reverse("delete_review", args=[self.review.id]))
1✔
658
        self.assertEqual(response.status_code, 302)
1✔
659
        self.assertFalse(Review.objects.filter(id=self.review.id).exists())
1✔
660

661
    def test_delete_image(self):
1✔
662
        response = self.client.post(reverse("delete_image", args=[self.image.id]))
1✔
663
        self.assertEqual(response.status_code, 302)
1✔
664
        self.assertFalse(ParkImage.objects.filter(id=self.image.id).exists())
1✔
665

666

667
class ParkImageModelTest(TestCase):
1✔
668
    def setUp(self):
1✔
669
        """Set up a test park and associated images."""
670
        self.park = DogRunNew.objects.create(
1✔
671
            id="1",
672
            prop_id="1234",
673
            name="Central Park",
674
            address="New York, NY",
675
            dogruns_type="Small",
676
            accessible="Yes",
677
            notes="Test park notes",
678
            google_name="Central Park",
679
            borough="M",
680
            zip_code="United States",
681
            formatted_address="Central Pk N, New York, NY, USA",
682
            latitude=40.7987768,
683
            longitude=-73.9537196,
684
            additional={
685
                "geometry": {
686
                    "bounds": {
687
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
688
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
689
                    },
690
                    "location": {"lat": 40.7987768, "lng": -73.9537196},
691
                    "location_type": "GEOMETRIC_CENTER",
692
                    "viewport": {
693
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
694
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
695
                    },
696
                }
697
            },
698
        )
699
        self.image = ParkImage.objects.create(
1✔
700
            park=self.park,
701
            image="https://res.cloudinary.com/demo/image/upload/sample.jpg",
702
        )
703

704
    def test_park_image_creation(self):
1✔
705
        """Test that a ParkImage object is created successfully."""
706
        self.assertEqual(self.image.park, self.park)
1✔
707
        self.assertEqual(
1✔
708
            self.image.image, "https://res.cloudinary.com/demo/image/upload/sample.jpg"
709
        )
710

711
    def test_park_image_str(self):
1✔
712
        """Test the string representation of a ParkImage object."""
713
        self.assertEqual(str(self.image), f"Image for {self.park.name}")
1✔
714

715

716
class ParkDetailViewImageTest(TestCase):
1✔
717
    def setUp(self):
1✔
718
        """Set up a test park and associated images."""
719
        self.client = Client()
1✔
720
        self.park = DogRunNew.objects.create(
1✔
721
            id="1",
722
            prop_id="1234",
723
            name="Central Park",
724
            address="New York, NY",
725
            dogruns_type="Small",
726
            accessible="Yes",
727
            notes="Test park notes",
728
            google_name="Central Park",
729
            borough="M",
730
            zip_code="United States",
731
            formatted_address="Central Pk N, New York, NY, USA",
732
            latitude=40.7987768,
733
            longitude=-73.9537196,
734
            additional={
735
                "geometry": {
736
                    "bounds": {
737
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
738
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
739
                    },
740
                    "location": {"lat": 40.7987768, "lng": -73.9537196},
741
                    "location_type": "GEOMETRIC_CENTER",
742
                    "viewport": {
743
                        "northeast": {"lat": 40.8009264, "lng": -73.9495752},
744
                        "southwest": {"lat": 40.796948, "lng": -73.9580246},
745
                    },
746
                }
747
            },
748
        )
749
        self.image = ParkImage.objects.create(
1✔
750
            park=self.park,
751
            image="https://res.cloudinary.com/demo/image/upload/sample.jpg",
752
        )
753

754
    def test_park_detail_view_with_images(self):
1✔
755
        """Test that the park detail view displays associated images."""
756
        response = self.client.get(
1✔
757
            reverse("park_detail", args=[self.park.slug, self.park.id])
758
        )
759
        self.assertEqual(response.status_code, 200)
1✔
760
        self.assertContains(response, self.park.name)
1✔
761
        # self.assertIn(self.image.image, response.content.decode())
762

763

764
class ParkDetailDisplayedReviewsTests(TestCase):
1✔
765
    def setUp(self):
1✔
766
        self.client = Client()
1✔
767
        self.user = User.objects.create_user(username="reviewer", password="pass123")
1✔
768

769
        self.park = DogRunNew.objects.create(
1✔
770
            id="10",
771
            prop_id="PARK100",
772
            name="Test Park",
773
            address="100 Test St",
774
            dogruns_type="Run",
775
            accessible="Yes",
776
            notes="Some notes",
777
            google_name="Test Park",
778
            borough="M",
779
            zip_code="10001",
780
            formatted_address="100 Test St, New York, NY",
781
            latitude=40.7128,
782
            longitude=-74.0060,
783
            display_name="Test Park",
784
            slug=slugify("Test Park-PARK100"),
785
        )
786

787
        # One visible review
788
        self.review_visible = Review.objects.create(
1✔
789
            park=self.park,
790
            user=self.user,
791
            text="This park is great!",
792
            rating=5,
793
            is_removed=False,
794
        )
795

796
        # One soft-deleted review
797
        self.review_removed = Review.objects.create(
1✔
798
            park=self.park,
799
            user=self.user,
800
            text="This review should be hidden",
801
            rating=1,
802
            is_removed=True,
803
        )
804

805
    def test_only_visible_reviews_displayed(self):
1✔
806
        url = reverse("park_detail", args=[self.park.slug, self.park.id])
1✔
807
        response = self.client.get(url)
1✔
808

809
        self.assertEqual(response.status_code, 200)
1✔
810
        self.assertContains(response, self.review_visible.text)
1✔
811
        self.assertNotContains(response, self.review_removed.text)
1✔
812

813
    def test_average_rating_excludes_removed_reviews(self):
1✔
814
        url = reverse("park_detail", args=[self.park.slug, self.park.id])
1✔
815
        response = self.client.get(url)
1✔
816

817
        # Ensure the average is based only on the 5-star review
818
        self.assertContains(response, "5.0")
1✔
819

820

821
class RenderStarsTests(TestCase):
1✔
822
    def test_int_stars(self):
1✔
823
        size = 20
1✔
824
        result = render_stars(4, size)
1✔
825
        self.assertEqual(result["filled_stars"], 4)
1✔
826
        self.assertEqual(result["half_stars"], 0)
1✔
827
        self.assertEqual(result["empty_stars"], 1)
1✔
828
        self.assertEqual(result["size"], size)
1✔
829

830
    def test_full_stars(self):
1✔
831
        size = 15
1✔
832
        result = render_stars(5, size)
1✔
833
        self.assertEqual(result["filled_stars"], 5)
1✔
834
        self.assertEqual(result["half_stars"], 0)
1✔
835
        self.assertEqual(result["empty_stars"], 0)
1✔
836
        self.assertEqual(result["size"], size)
1✔
837

838
    def test_no_stars(self):
1✔
839
        size = 10
1✔
840
        result = render_stars(0, size)
1✔
841
        self.assertEqual(result["filled_stars"], 0)
1✔
842
        self.assertEqual(result["half_stars"], 0)
1✔
843
        self.assertEqual(result["empty_stars"], 5)
1✔
844
        self.assertEqual(result["size"], size)
1✔
845

846
    def test_half_stars(self):
1✔
847
        size = 20
1✔
848
        result = render_stars(2.5, size)
1✔
849
        self.assertEqual(result["filled_stars"], 2)
1✔
850
        self.assertEqual(result["half_stars"], 1)
1✔
851
        self.assertEqual(result["empty_stars"], 2)
1✔
852
        self.assertEqual(result["size"], size)
1✔
853

854
    # >= X.25 -> one half star
855
    def test_round_up_to_half(self):
1✔
856
        size = 20
1✔
857
        result = render_stars(4.25, size)
1✔
858
        self.assertEqual(result["filled_stars"], 4)
1✔
859
        self.assertEqual(result["half_stars"], 1)
1✔
860
        self.assertEqual(result["empty_stars"], 0)
1✔
861
        self.assertEqual(result["size"], size)
1✔
862

863
    # < X.25 -> round down to whole
864
    def test_round_down_to_whole(self):
1✔
865
        size = 20
1✔
866
        result = render_stars(3.24, size)
1✔
867
        self.assertEqual(result["filled_stars"], 3)
1✔
868
        self.assertEqual(result["half_stars"], 0)
1✔
869
        self.assertEqual(result["empty_stars"], 2)
1✔
870
        self.assertEqual(result["size"], size)
1✔
871

872
    # < X.75 -> one half star
873
    def test_round_down_to_half(self):
1✔
874
        size = 20
1✔
875
        result = render_stars(2.74, size)
1✔
876
        self.assertEqual(result["filled_stars"], 2)
1✔
877
        self.assertEqual(result["half_stars"], 1)
1✔
878
        self.assertEqual(result["empty_stars"], 2)
1✔
879
        self.assertEqual(result["size"], size)
1✔
880

881
    # >= X.75 -> round up to next whole
882
    def test_round_up_to_whole(self):
1✔
883
        size = 20
1✔
884
        result = render_stars(4.75, size)
1✔
885
        self.assertEqual(result["filled_stars"], 5)
1✔
886
        self.assertEqual(result["half_stars"], 0)
1✔
887
        self.assertEqual(result["empty_stars"], 0)
1✔
888
        self.assertEqual(result["size"], size)
1✔
889

890

891
class ParkPresenceTests(TestCase):
1✔
892
    def setUp(self):
1✔
893
        self.client = Client()
1✔
894
        self.user = User.objects.create_user(username="tester", password="testpass")
1✔
895
        self.park = DogRunNew.objects.create(
1✔
896
            id="5",
897
            prop_id="5566",
898
            name="Test Dog Park",
899
            address="Test Location",
900
            dogruns_type="All",
901
            accessible="Yes",
902
            formatted_address="Test Address",
903
            latitude=40.0,
904
            longitude=-73.0,
905
            display_name="Test Dog Park",
906
            slug="test-dog-park-5566",
907
        )
908
        self.client.login(username="tester", password="testpass")
1✔
909

910
    def test_user_check_in_creates_presence(self):
1✔
911
        self.client.post(
1✔
912
            reverse("park_detail", args=[self.park.slug, self.park.id]),
913
            {"form_type": "check_in"},
914
        )
915
        presences = ParkPresence.objects.filter(user=self.user, park=self.park)
1✔
916
        self.assertEqual(presences.count(), 1)
1✔
917
        self.assertEqual(presences.first().status, "current")
1✔
918

919
    def test_user_be_there_at_creates_presence(self):
1✔
920
        future_time = (timezone.now() + timedelta(minutes=20)).strftime("%H:%M")
1✔
921
        self.client.post(
1✔
922
            reverse("park_detail", args=[self.park.slug, self.park.id]),
923
            {"form_type": "be_there_at", "time": future_time},
924
        )
925
        presences = ParkPresence.objects.filter(user=self.user, park=self.park)
1✔
926
        self.assertEqual(presences.count(), 1)
1✔
927
        self.assertEqual(presences.first().status, "on_the_way")
1✔
928

929

930
@patch(
1✔
931
    "cloudinary.uploader.upload",
932
    return_value={
933
        "asset_id": "dummy_asset_id",
934
        "public_id": "dummy_id",
935
        "version": "1234567890",
936
        "signature": "dummy_signature",
937
        "width": 800,
938
        "height": 600,
939
        "format": "jpg",
940
        "resource_type": "image",
941
        "type": "upload",
942
        "secure_url": "https://dummy.cloudinary.com/image.jpg",
943
        "url": "http://dummy.cloudinary.com/image.jpg",
944
    },
945
)
946
class ImageUploadTests(TestCase):
1✔
947
    def setUp(self):
1✔
948
        self.client = Client()
1✔
949
        self.user = User.objects.create_user(username="uploader", password="pass123")
1✔
950
        self.client.login(username="uploader", password="pass123")
1✔
951
        self.park = DogRunNew.objects.create(
1✔
952
            id="20",
953
            prop_id="8888",
954
            name="Mock Park",
955
            address="123",
956
            dogruns_type="All",
957
            accessible="Yes",
958
            formatted_address="123",
959
            latitude=40.0,
960
            longitude=-73.0,
961
            slug="mock-park-8888",
962
            display_name="Mock Park",
963
        )
964

965
    def test_upload_image_with_review(self, mock_upload):
1✔
966
        cloudinary_config(
1✔
967
            cloud_name="demo",
968
            api_key="fake_api_key",
969
            api_secret="fake_api_secret",
970
        )
971

972
        image = SimpleUploadedFile(
1✔
973
            "test.jpg", b"file_content", content_type="image/jpeg"
974
        )
975

976
        response = self.client.post(
1✔
977
            reverse("park_detail", args=[self.park.slug, self.park.id]),
978
            {
979
                "form_type": "submit_review",
980
                "text": "Nice park!",
981
                "rating": "5",
982
                "images": image,
983
            },
984
            follow=True,
985
        )
986

987
        self.assertEqual(response.status_code, 200)
1✔
988
        self.assertEqual(ParkImage.objects.count(), 1)
1✔
989

990

991
class ModalInteractionTests(TestCase):
1✔
992
    def test_modal_js_is_present(self):
1✔
993
        client = Client()
1✔
994
        park = DogRunNew.objects.create(
1✔
995
            id="7",
996
            prop_id="3344",
997
            name="Modal Park",
998
            address="JSville",
999
            dogruns_type="Small",
1000
            accessible="Yes",
1001
            formatted_address="JS Road",
1002
            latitude=42.0,
1003
            longitude=-75.0,
1004
            display_name="Modal Park",
1005
            slug="modal-park-3344",
1006
        )
1007
        response = client.get(reverse("park_detail", args=[park.slug, park.id]))
1✔
1008
        self.assertContains(response, "function openCarouselImageModal")
1✔
1009
        self.assertContains(response, "imagePreviewModal")
1✔
1010
        self.assertContains(response, "modalImage")
1✔
1011

1012

1013
class ReplaceFilterTests(TestCase):
1✔
1014
    def test_replace_basic(self):
1✔
1015
        result = image_filters.replace("hello world", "world,there")
1✔
1016
        self.assertEqual(result, "hello there")
1✔
1017

1018
    def test_replace_partial_match(self):
1✔
1019
        result = image_filters.replace("abcabcabc", "a,x")
1✔
1020
        self.assertEqual(result, "xbcxbcxbc")
1✔
1021

1022
    def test_replace_only_first_comma_splits(self):
1✔
1023
        result = image_filters.replace("one,two,three", "two,2")
1✔
1024
        self.assertEqual(result, "one,2,three")
1✔
1025

1026
    def test_replace_with_comma_in_replacement(self):
1✔
1027
        result = image_filters.replace("item1,item2", "item1,x,y")
1✔
1028
        self.assertEqual(result, "x,y,item2")  # Splits only on first comma
1✔
1029

1030
    def test_replace_no_match(self):
1✔
1031
        result = image_filters.replace("hello", "z,x")
1✔
1032
        self.assertEqual(result, "hello")
1✔
1033

1034

1035
class ReplyViewTests(TestCase):
1✔
1036
    def setUp(self):
1✔
1037
        self.client = Client()
1✔
1038
        self.user = User.objects.create_user(username="testuser", password="testpass")
1✔
1039
        self.park = DogRunNew.objects.create(
1✔
1040
            id="99",
1041
            prop_id="9999",
1042
            name="Reply Park",
1043
            address="Somewhere",
1044
            dogruns_type="All",
1045
            accessible="Yes",
1046
            formatted_address="Reply Address",
1047
            latitude=40.0,
1048
            longitude=-73.0,
1049
            slug="reply-park-9999",
1050
        )
1051
        self.review = Review.objects.create(
1✔
1052
            park=self.park, text="Original Review", rating=4, user=self.user
1053
        )
1054
        self.park_detail_url = reverse(
1✔
1055
            "park_detail", args=[self.park.slug, self.park.id]
1056
        )
1057

1058
    def test_submit_reply_to_review(self):
1✔
1059
        self.client.login(username="testuser", password="testpass")
1✔
1060
        response = self.client.post(
1✔
1061
            self.park_detail_url,
1062
            {
1063
                "form_type": "submit_reply",
1064
                "parent_review_id": self.review.id,
1065
                "reply_text": "This is a reply to a review.",
1066
            },
1067
        )
1068
        self.assertEqual(response.status_code, 302)
1✔
1069
        self.assertTrue(
1✔
1070
            Reply.objects.filter(
1071
                review=self.review, text="This is a reply to a review."
1072
            ).exists()
1073
        )
1074

1075
    def test_submit_nested_reply_to_reply(self):
1✔
1076
        parent_reply = Reply.objects.create(
1✔
1077
            review=self.review, user=self.user, text="Parent reply"
1078
        )
1079
        self.client.login(username="testuser", password="testpass")
1✔
1080
        response = self.client.post(
1✔
1081
            self.park_detail_url,
1082
            {
1083
                "form_type": "submit_reply",
1084
                "parent_review_id": self.review.id,
1085
                "parent_reply_id": parent_reply.id,
1086
                "reply_text": "Child reply",
1087
            },
1088
        )
1089
        self.assertEqual(response.status_code, 302)
1✔
1090
        self.assertTrue(
1✔
1091
            Reply.objects.filter(parent_reply=parent_reply, text="Child reply").exists()
1092
        )
1093

1094
    def test_submit_reply_with_invalid_parent_reply_id(self):
1✔
1095
        self.client.login(username="testuser", password="testpass")
1✔
1096
        response = self.client.post(
1✔
1097
            self.park_detail_url,
1098
            {
1099
                "form_type": "submit_reply",
1100
                "parent_review_id": self.review.id,
1101
                "parent_reply_id": 9999,
1102
                "reply_text": "Fallback to review",
1103
            },
1104
        )
1105
        self.assertEqual(response.status_code, 302)
1✔
1106
        reply = Reply.objects.get(text="Fallback to review")
1✔
1107
        self.assertIsNone(reply.parent_reply)
1✔
1108

1109
    def test_submit_reply_without_text(self):
1✔
1110
        self.client.login(username="testuser", password="testpass")
1✔
1111
        response = self.client.post(
1✔
1112
            self.park_detail_url,
1113
            {
1114
                "form_type": "submit_reply",
1115
                "parent_review_id": self.review.id,
1116
                "reply_text": "   ",
1117
            },
1118
        )
1119
        self.assertEqual(response.status_code, 302)
1✔
1120
        self.assertEqual(Reply.objects.filter(review=self.review).count(), 0)
1✔
1121

1122
    def test_submit_reply_unauthenticated(self):
1✔
1123
        response = self.client.post(
1✔
1124
            self.park_detail_url,
1125
            {
1126
                "form_type": "submit_reply",
1127
                "parent_review_id": self.review.id,
1128
                "reply_text": "Unauthorized reply",
1129
            },
1130
        )
1131
        self.assertEqual(response.status_code, 200)
1✔
1132
        self.assertNotIn("Reply submitted successfully", response.content.decode())
1✔
1133

1134
    def test_delete_own_reply(self):
1✔
1135
        self.client.login(username="testuser", password="testpass")
1✔
1136
        reply = Reply.objects.create(
1✔
1137
            review=self.review, user=self.user, text="To be deleted"
1138
        )
1139
        response = self.client.post(reverse("delete_reply", args=[reply.id]))
1✔
1140
        self.assertEqual(response.status_code, 302)
1✔
1141
        self.assertFalse(Reply.objects.filter(id=reply.id).exists())
1✔
1142

1143
    def test_delete_others_reply_forbidden(self):
1✔
1144
        other = User.objects.create_user(username="other", password="pass")
1✔
1145
        reply = Reply.objects.create(review=self.review, user=other, text="Not yours")
1✔
1146
        self.client.login(username="testuser", password="testpass")
1✔
1147
        response = self.client.post(reverse("delete_reply", args=[reply.id]))
1✔
1148
        self.assertEqual(response.status_code, 302)
1✔
1149
        self.assertTrue(Reply.objects.filter(id=reply.id).exists())
1✔
1150

1151
    def test_report_reply_success(self):
1✔
1152
        other = User.objects.create_user(username="other", password="pass")
1✔
1153
        reply = Reply.objects.create(review=self.review, user=other, text="Report me")
1✔
1154
        self.client.login(username="testuser", password="testpass")
1✔
1155
        response = self.client.post(
1✔
1156
            reverse("report_reply", args=[reply.id]), {"reason": "Spam"}
1157
        )
1158
        self.assertEqual(response.status_code, 302)
1✔
1159
        self.assertTrue(reply.reports.exists())
1✔
1160

1161
    def test_report_own_reply_fails(self):
1✔
1162
        reply = Reply.objects.create(
1✔
1163
            review=self.review, user=self.user, text="Self report"
1164
        )
1165
        self.client.login(username="testuser", password="testpass")
1✔
1166
        response = self.client.post(
1✔
1167
            reverse("report_reply", args=[reply.id]), {"reason": "Oops"}
1168
        )
1169
        self.assertEqual(response.status_code, 302)
1✔
1170
        self.assertEqual(reply.reports.count(), 0)
1✔
1171

1172

1173
class ChatViewTests(TestCase):
1✔
1174
    def setUp(self):
1✔
1175
        self.user1 = User.objects.create_user(username="user1", password="pass1234")
1✔
1176
        self.user2 = User.objects.create_user(username="user2", password="pass1234")
1✔
1177

1178
    def test_chat_view_get(self):
1✔
1179
        self.client.login(username="user1", password="pass1234")
1✔
1180
        response = self.client.get(reverse("chat_view", args=[self.user2.username]))
1✔
1181
        self.assertEqual(response.status_code, 200)
1✔
1182
        self.assertContains(response, self.user2.username)
1✔
1183

1184
    def test_chat_view_post(self):
1✔
1185
        self.client.login(username="user1", password="pass1234")
1✔
1186
        response = self.client.post(
1✔
1187
            reverse("chat_view", args=[self.user2.username]),
1188
            {"content": "Hello user2!"},
1189
        )
1190
        self.assertEqual(response.status_code, 302)  # Redirect after POST
1✔
1191

1192
        # Verify the message was saved
1193
        messages = Message.objects.filter(sender=self.user1, recipient=self.user2)
1✔
1194
        self.assertEqual(messages.count(), 1)
1✔
1195
        self.assertEqual(messages.first().content, "Hello user2!")
1✔
1196

1197

1198
class AllMessagesViewTests(TestCase):
1✔
1199
    def setUp(self):
1✔
1200
        self.user1 = User.objects.create_user(username="user1", password="pass1234")
1✔
1201
        self.user2 = User.objects.create_user(username="user2", password="pass1234")
1✔
1202
        self.user3 = User.objects.create_user(username="user3", password="pass1234")
1✔
1203

1204
        # Create messages between user1 and user2
1205
        Message.objects.create(sender=self.user1, recipient=self.user2, content="Hi 2")
1✔
1206
        Message.objects.create(sender=self.user2, recipient=self.user1, content="Hey 1")
1✔
1207

1208
        # Create messages between user1 and user3
1209
        Message.objects.create(
1✔
1210
            sender=self.user3, recipient=self.user1, content="Hello from 3"
1211
        )
1212

1213
    def test_all_messages_view(self):
1✔
1214
        self.client.login(username="user1", password="pass1234")
1✔
1215
        response = self.client.get(reverse("all_messages"))
1✔
1216
        self.assertEqual(response.status_code, 200)
1✔
1217
        self.assertContains(response, "user2")
1✔
1218
        self.assertContains(response, "user3")
1✔
1219

1220
    def test_grouping_of_messages(self):
1✔
1221
        self.client.login(username="user1", password="pass1234")
1✔
1222
        response = self.client.get(reverse("all_messages"))
1✔
1223
        self.assertEqual(response.status_code, 200)
1✔
1224
        context = response.context["grouped_messages"]
1✔
1225
        self.assertIn("user2", context)
1✔
1226
        self.assertIn("user3", context)
1✔
1227
        self.assertEqual(len(context["user2"]), 2)
1✔
1228
        self.assertEqual(len(context["user3"]), 1)
1✔
1229

1230

1231
class DeleteConversationTests(TestCase):
1✔
1232
    def setUp(self):
1✔
1233
        self.user1 = User.objects.create_user(username="user1", password="pass1234")
1✔
1234
        self.user2 = User.objects.create_user(username="user2", password="pass1234")
1✔
1235

1236
        # Create some messages between user1 and user2
1237
        Message.objects.create(
1✔
1238
            sender=self.user1,
1239
            recipient=self.user2,
1240
            content="Hello",
1241
            timestamp=timezone.now(),
1242
        )
1243
        Message.objects.create(
1✔
1244
            sender=self.user2,
1245
            recipient=self.user1,
1246
            content="Hi",
1247
            timestamp=timezone.now() + timedelta(minutes=1),
1248
        )
1249
        Message.objects.create(
1✔
1250
            sender=self.user1,
1251
            recipient=self.user2,
1252
            content="How are you?",
1253
            timestamp=timezone.now() + timedelta(minutes=2),
1254
        )
1255

1256
    def test_delete_conversation_deletes_messages(self):
1✔
1257
        self.client.login(username="user1", password="pass1234")
1✔
1258

1259
        # Ensure messages exist
1260
        self.assertEqual(Message.objects.count(), 3)
1✔
1261

1262
        url = reverse("delete_conversation", args=[self.user2.username])
1✔
1263
        response = self.client.post(url)
1✔
1264

1265
        # Should redirect to all_messages
1266
        self.assertEqual(response.status_code, 302)
1✔
1267
        self.assertRedirects(response, reverse("all_messages"))
1✔
1268

1269
        # All messages between user1 and user2 should be gone
1270
        self.assertEqual(Message.objects.count(), 0)
1✔
1271

1272
    def test_delete_conversation_requires_login(self):
1✔
1273
        url = reverse("delete_conversation", args=[self.user2.username])
1✔
1274
        response = self.client.post(url)
1✔
1275

1276
        # Should redirect to login page
1277
        self.assertEqual(response.status_code, 302)
1✔
1278
        self.assertIn("/login", response.url)  # Adjust if you have a custom login URL
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc