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

rafalp / Misago / 9238384161

25 May 2024 09:38PM UTC coverage: 97.615% (-1.1%) from 98.716%
9238384161

Pull #1742

github

web-flow
Merge 9b9215592 into abad4f068
Pull Request #1742: Replace forum options with account settings

192 of 242 new or added lines in 20 files covered. (79.34%)

660 existing lines in 146 files now uncovered.

51658 of 52920 relevant lines covered (97.62%)

0.98 hits per line

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

99.82
/misago/threads/tests/test_thread_postpatch_api.py
1
import json
1✔
2
from datetime import timedelta
1✔
3

4
from django.urls import reverse
1✔
5
from django.utils import timezone
1✔
6

7
from .. import test
1✔
8
from ...categories.models import Category
1✔
9
from ...users.test import AuthenticatedUserTestCase
1✔
10
from ..models import Post
1✔
11
from ..test import patch_category_acl
1✔
12

13

14
class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
1✔
15
    def setUp(self):
1✔
16
        super().setUp()
1✔
17

18
        self.category = Category.objects.get(slug="first-category")
1✔
19
        self.thread = test.post_thread(category=self.category)
1✔
20
        self.post = test.reply_thread(self.thread, poster=self.user)
1✔
21

22
        self.api_link = reverse(
1✔
23
            "misago:api:thread-post-detail",
24
            kwargs={"thread_pk": self.thread.pk, "pk": self.post.pk},
25
        )
26

27
    def patch(self, api_link, ops):
1✔
28
        return self.client.patch(
1✔
29
            api_link, json.dumps(ops), content_type="application/json"
30
        )
31

32

33
class PostAddAclApiTests(ThreadPostPatchApiTestCase):
1✔
34
    def test_add_acl_true(self):
1✔
35
        """api adds current event's acl to response"""
36
        response = self.patch(
1✔
37
            self.api_link, [{"op": "add", "path": "acl", "value": True}]
38
        )
39
        self.assertEqual(response.status_code, 200)
1✔
40

41
        response_json = response.json()
1✔
42
        self.assertTrue(response_json["acl"])
1✔
43

44
    def test_add_acl_false(self):
1✔
45
        """
46
        if value is false, api won't add acl to the response, but will set empty key
47
        """
48
        response = self.patch(
1✔
49
            self.api_link, [{"op": "add", "path": "acl", "value": False}]
50
        )
51
        self.assertEqual(response.status_code, 200)
1✔
52

53
        response_json = response.json()
1✔
54
        self.assertIsNone(response_json["acl"])
1✔
55

56

57
class PostProtectApiTests(ThreadPostPatchApiTestCase):
1✔
58
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
1✔
59
    def test_protect_post(self):
1✔
60
        """api makes it possible to protect post"""
61
        response = self.patch(
1✔
62
            self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
63
        )
64
        self.assertEqual(response.status_code, 200)
1✔
65

66
        reponse_json = response.json()
1✔
67
        self.assertTrue(reponse_json["is_protected"])
1✔
68

69
        self.post.refresh_from_db()
1✔
70
        self.assertTrue(self.post.is_protected)
1✔
71

72
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
1✔
73
    def test_unprotect_post(self):
1✔
74
        """api makes it possible to unprotect protected post"""
75
        self.post.is_protected = True
1✔
76
        self.post.save()
1✔
77

78
        response = self.patch(
1✔
79
            self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
80
        )
81
        self.assertEqual(response.status_code, 200)
1✔
82

83
        reponse_json = response.json()
1✔
84
        self.assertFalse(reponse_json["is_protected"])
1✔
85

86
        self.post.refresh_from_db()
1✔
87
        self.assertFalse(self.post.is_protected)
1✔
88

89
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
1✔
90
    def test_protect_best_answer(self):
1✔
91
        """api makes it possible to protect post"""
92
        self.thread.set_best_answer(self.user, self.post)
1✔
93
        self.thread.save()
1✔
94

95
        self.assertFalse(self.thread.best_answer_is_protected)
1✔
96

97
        response = self.patch(
1✔
98
            self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
99
        )
100
        self.assertEqual(response.status_code, 200)
1✔
101

102
        reponse_json = response.json()
1✔
103
        self.assertTrue(reponse_json["is_protected"])
1✔
104

105
        self.post.refresh_from_db()
1✔
106
        self.assertTrue(self.post.is_protected)
1✔
107

108
        self.thread.refresh_from_db()
1✔
109
        self.assertTrue(self.thread.best_answer_is_protected)
1✔
110

111
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": True})
1✔
112
    def test_unprotect_best_answer(self):
1✔
113
        """api makes it possible to unprotect protected post"""
114
        self.post.is_protected = True
1✔
115
        self.post.save()
1✔
116

117
        self.thread.set_best_answer(self.user, self.post)
1✔
118
        self.thread.save()
1✔
119

120
        self.assertTrue(self.thread.best_answer_is_protected)
1✔
121

122
        response = self.patch(
1✔
123
            self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
124
        )
125
        self.assertEqual(response.status_code, 200)
1✔
126

127
        reponse_json = response.json()
1✔
128
        self.assertFalse(reponse_json["is_protected"])
1✔
129

130
        self.post.refresh_from_db()
1✔
131
        self.assertFalse(self.post.is_protected)
1✔
132

133
        self.thread.refresh_from_db()
1✔
134
        self.assertFalse(self.thread.best_answer_is_protected)
1✔
135

136
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": False})
1✔
137
    def test_protect_post_no_permission(self):
1✔
138
        """api validates permission to protect post"""
139
        response = self.patch(
1✔
140
            self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
141
        )
142
        self.assertEqual(response.status_code, 400)
1✔
143

144
        response_json = response.json()
1✔
145
        self.assertEqual(
1✔
146
            response_json["detail"][0], "You can't protect posts in this category."
147
        )
148

149
        self.post.refresh_from_db()
1✔
150
        self.assertFalse(self.post.is_protected)
1✔
151

152
    @patch_category_acl({"can_edit_posts": 2, "can_protect_posts": False})
1✔
153
    def test_unprotect_post_no_permission(self):
1✔
154
        """api validates permission to unprotect post"""
155
        self.post.is_protected = True
1✔
156
        self.post.save()
1✔
157

158
        response = self.patch(
1✔
159
            self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
160
        )
161
        self.assertEqual(response.status_code, 400)
1✔
162

163
        response_json = response.json()
1✔
164
        self.assertEqual(
1✔
165
            response_json["detail"][0], "You can't protect posts in this category."
166
        )
167

168
        self.post.refresh_from_db()
1✔
169
        self.assertTrue(self.post.is_protected)
1✔
170

171
    @patch_category_acl({"can_edit_posts": 0, "can_protect_posts": True})
1✔
172
    def test_protect_post_not_editable(self):
1✔
173
        """api validates if we can edit post we want to protect"""
174
        response = self.patch(
1✔
175
            self.api_link, [{"op": "replace", "path": "is-protected", "value": True}]
176
        )
177
        self.assertEqual(response.status_code, 400)
1✔
178

179
        response_json = response.json()
1✔
180
        self.assertEqual(
1✔
181
            response_json["detail"][0], "You can't protect posts you can't edit."
182
        )
183

184
        self.post.refresh_from_db()
1✔
185
        self.assertFalse(self.post.is_protected)
1✔
186

187
    @patch_category_acl({"can_edit_posts": 0, "can_protect_posts": True})
1✔
188
    def test_unprotect_post_not_editable(self):
1✔
189
        """api validates if we can edit post we want to protect"""
190
        self.post.is_protected = True
1✔
191
        self.post.save()
1✔
192

193
        response = self.patch(
1✔
194
            self.api_link, [{"op": "replace", "path": "is-protected", "value": False}]
195
        )
196
        self.assertEqual(response.status_code, 400)
1✔
197

198
        response_json = response.json()
1✔
199
        self.assertEqual(
1✔
200
            response_json["detail"][0], "You can't protect posts you can't edit."
201
        )
202

203
        self.post.refresh_from_db()
1✔
204
        self.assertTrue(self.post.is_protected)
1✔
205

206

207
class PostApproveApiTests(ThreadPostPatchApiTestCase):
1✔
208
    @patch_category_acl({"can_approve_content": True})
1✔
209
    def test_approve_post(self):
1✔
210
        """api makes it possible to approve post"""
211
        self.post.is_unapproved = True
1✔
212
        self.post.save()
1✔
213

214
        response = self.patch(
1✔
215
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
216
        )
217
        self.assertEqual(response.status_code, 200)
1✔
218

219
        reponse_json = response.json()
1✔
220
        self.assertFalse(reponse_json["is_unapproved"])
1✔
221

222
        self.post.refresh_from_db()
1✔
223
        self.assertFalse(self.post.is_unapproved)
1✔
224

225
    @patch_category_acl({"can_approve_content": True})
1✔
226
    def test_unapprove_post(self):
1✔
227
        """unapproving posts is not supported by api"""
228
        response = self.patch(
1✔
229
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": True}]
230
        )
231
        self.assertEqual(response.status_code, 400)
1✔
232

233
        response_json = response.json()
1✔
234
        self.assertEqual(
1✔
235
            response_json["detail"][0], "Content approval can't be reversed."
236
        )
237

238
        self.post.refresh_from_db()
1✔
239
        self.assertFalse(self.post.is_unapproved)
1✔
240

241
    @patch_category_acl({"can_approve_content": False})
1✔
242
    def test_approve_post_no_permission(self):
1✔
243
        """api validates approval permission"""
244
        self.post.is_unapproved = True
1✔
245
        self.post.save()
1✔
246

247
        response = self.patch(
1✔
248
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
249
        )
250
        self.assertEqual(response.status_code, 400)
1✔
251

252
        response_json = response.json()
1✔
253
        self.assertEqual(
1✔
254
            response_json["detail"][0], "You can't approve posts in this category."
255
        )
256

257
        self.post.refresh_from_db()
1✔
258
        self.assertTrue(self.post.is_unapproved)
1✔
259

260
    @patch_category_acl({"can_approve_content": True, "can_close_threads": False})
1✔
261
    def test_approve_post_closed_thread_no_permission(self):
1✔
262
        """api validates approval permission in closed threads"""
263
        self.post.is_unapproved = True
1✔
264
        self.post.save()
1✔
265

266
        self.thread.is_closed = True
1✔
267
        self.thread.save()
1✔
268

269
        response = self.patch(
1✔
270
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
271
        )
272
        self.assertEqual(response.status_code, 400)
1✔
273

274
        response_json = response.json()
1✔
275
        self.assertEqual(
1✔
276
            response_json["detail"][0],
277
            "This thread is closed. You can't approve posts in it.",
278
        )
279

280
        self.post.refresh_from_db()
1✔
281
        self.assertTrue(self.post.is_unapproved)
1✔
282

283
    @patch_category_acl({"can_approve_content": True, "can_close_threads": False})
1✔
284
    def test_approve_post_closed_category_no_permission(self):
1✔
285
        """api validates approval permission in closed categories"""
286
        self.post.is_unapproved = True
1✔
287
        self.post.save()
1✔
288

289
        self.category.is_closed = True
1✔
290
        self.category.save()
1✔
291

292
        response = self.patch(
1✔
293
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
294
        )
295
        self.assertEqual(response.status_code, 400)
1✔
296

297
        response_json = response.json()
1✔
298
        self.assertEqual(
1✔
299
            response_json["detail"][0],
300
            "This category is closed. You can't approve posts in it.",
301
        )
302

303
        self.post.refresh_from_db()
1✔
304
        self.assertTrue(self.post.is_unapproved)
1✔
305

306
    @patch_category_acl({"can_approve_content": True})
1✔
307
    def test_approve_first_post(self):
1✔
308
        """api approve first post fails"""
309
        self.post.is_unapproved = True
1✔
310
        self.post.save()
1✔
311

312
        self.thread.set_first_post(self.post)
1✔
313
        self.thread.save()
1✔
314

315
        response = self.patch(
1✔
316
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
317
        )
318
        self.assertEqual(response.status_code, 400)
1✔
319

320
        response_json = response.json()
1✔
321
        self.assertEqual(
1✔
322
            response_json["detail"][0],
323
            "Thread's first post can only be approved together with thread.",
324
        )
325

326
        self.post.refresh_from_db()
1✔
327
        self.assertTrue(self.post.is_unapproved)
1✔
328

329
    @patch_category_acl({"can_approve_content": True})
1✔
330
    def test_approve_hidden_post(self):
1✔
331
        """api approve hidden post fails"""
332
        self.post.is_unapproved = True
1✔
333
        self.post.is_hidden = True
1✔
334
        self.post.save()
1✔
335

336
        response = self.patch(
1✔
337
            self.api_link, [{"op": "replace", "path": "is-unapproved", "value": False}]
338
        )
339
        self.assertEqual(response.status_code, 400)
1✔
340

341
        response_json = response.json()
1✔
342
        self.assertEqual(
1✔
343
            response_json["detail"][0],
344
            "You can't approve posts the content you can't see.",
345
        )
346

347
        self.post.refresh_from_db()
1✔
348
        self.assertTrue(self.post.is_unapproved)
1✔
349

350

351
class PostHideApiTests(ThreadPostPatchApiTestCase):
1✔
352
    @patch_category_acl({"can_hide_posts": 1})
1✔
353
    def test_hide_post(self):
1✔
354
        """api makes it possible to hide post"""
355
        response = self.patch(
1✔
356
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
357
        )
358
        self.assertEqual(response.status_code, 200)
1✔
359

360
        reponse_json = response.json()
1✔
361
        self.assertTrue(reponse_json["is_hidden"])
1✔
362

363
        self.post.refresh_from_db()
1✔
364
        self.assertTrue(self.post.is_hidden)
1✔
365

366
    @patch_category_acl({"can_hide_posts": 1})
1✔
367
    def test_hide_own_post(self):
1✔
368
        """api makes it possible to hide owned post"""
369
        response = self.patch(
1✔
370
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
371
        )
372
        self.assertEqual(response.status_code, 200)
1✔
373

374
        reponse_json = response.json()
1✔
375
        self.assertTrue(reponse_json["is_hidden"])
1✔
376

377
        self.post.refresh_from_db()
1✔
378
        self.assertTrue(self.post.is_hidden)
1✔
379

380
    @patch_category_acl({"can_hide_posts": 0})
1✔
381
    def test_hide_post_no_permission(self):
1✔
382
        """api hide post with no permission fails"""
383
        response = self.patch(
1✔
384
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
385
        )
386
        self.assertEqual(response.status_code, 400)
1✔
387

388
        response_json = response.json()
1✔
389
        self.assertEqual(
1✔
390
            response_json["detail"][0], "You can't hide posts in this category."
391
        )
392

393
        self.post.refresh_from_db()
1✔
394
        self.assertFalse(self.post.is_hidden)
1✔
395

396
    @patch_category_acl({"can_hide_own_posts": 1, "can_protect_posts": False})
1✔
397
    def test_hide_own_protected_post(self):
1✔
398
        """api validates if we are trying to hide protected post"""
399
        self.post.is_protected = True
1✔
400
        self.post.save()
1✔
401

402
        response = self.patch(
1✔
403
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
404
        )
405
        self.assertEqual(response.status_code, 400)
1✔
406

407
        response_json = response.json()
1✔
408
        self.assertEqual(
1✔
409
            response_json["detail"][0], "This post is protected. You can't hide it."
410
        )
411

412
        self.post.refresh_from_db()
1✔
413
        self.assertFalse(self.post.is_hidden)
1✔
414

415
    @patch_category_acl({"can_hide_own_posts": True})
1✔
416
    def test_hide_other_user_post(self):
1✔
417
        """api validates post ownership when hiding"""
418
        self.post.poster = None
1✔
419
        self.post.save()
1✔
420

421
        response = self.patch(
1✔
422
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
423
        )
424
        self.assertEqual(response.status_code, 400)
1✔
425

426
        response_json = response.json()
1✔
427
        self.assertEqual(
1✔
428
            response_json["detail"][0],
429
            "You can't hide other users posts in this category.",
430
        )
431

432
        self.post.refresh_from_db()
1✔
433
        self.assertFalse(self.post.is_hidden)
1✔
434

435
    @patch_category_acl({"post_edit_time": 1, "can_hide_own_posts": True})
1✔
436
    def test_hide_own_post_after_edit_time(self):
1✔
437
        """api validates if we are trying to hide post after edit time"""
438
        self.post.posted_on = timezone.now() - timedelta(minutes=10)
1✔
439
        self.post.save()
1✔
440

441
        response = self.patch(
1✔
442
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
443
        )
444
        self.assertEqual(response.status_code, 400)
1✔
445

446
        response_json = response.json()
1✔
447
        self.assertEqual(
1✔
448
            response_json["detail"][0],
449
            "You can't hide posts that are older than 1 minute.",
450
        )
451

452
        self.post.refresh_from_db()
1✔
453
        self.assertFalse(self.post.is_hidden)
1✔
454

455
    @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": True})
1✔
456
    def test_hide_post_in_closed_thread(self):
1✔
457
        """api validates if we are trying to hide post in closed thread"""
458
        self.thread.is_closed = True
1✔
459
        self.thread.save()
1✔
460

461
        response = self.patch(
1✔
462
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
463
        )
464
        self.assertEqual(response.status_code, 400)
1✔
465

466
        response_json = response.json()
1✔
467
        self.assertEqual(
1✔
468
            response_json["detail"][0],
469
            "This thread is closed. You can't hide posts in it.",
470
        )
471

472
        self.post.refresh_from_db()
1✔
473
        self.assertFalse(self.post.is_hidden)
1✔
474

475
    @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": True})
1✔
476
    def test_hide_post_in_closed_category(self):
1✔
477
        """api validates if we are trying to hide post in closed category"""
478
        self.category.is_closed = True
1✔
479
        self.category.save()
1✔
480

481
        response = self.patch(
1✔
482
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
483
        )
484
        self.assertEqual(response.status_code, 400)
1✔
485

486
        response_json = response.json()
1✔
487
        self.assertEqual(
1✔
488
            response_json["detail"][0],
489
            "This category is closed. You can't hide posts in it.",
490
        )
491

492
        self.post.refresh_from_db()
1✔
493
        self.assertFalse(self.post.is_hidden)
1✔
494

495
    @patch_category_acl({"can_hide_posts": 1})
1✔
496
    def test_hide_first_post(self):
1✔
497
        """api hide first post fails"""
498
        self.thread.set_first_post(self.post)
1✔
499
        self.thread.save()
1✔
500

501
        response = self.patch(
1✔
502
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
503
        )
504
        self.assertEqual(response.status_code, 400)
1✔
505

506
        response_json = response.json()
1✔
507
        self.assertEqual(
1✔
508
            response_json["detail"][0],
509
            "Thread's first post can only be hidden using the hide thread option.",
510
        )
511

512
    @patch_category_acl({"can_hide_posts": 1})
1✔
513
    def test_hide_best_answer(self):
1✔
514
        """api hide first post fails"""
515
        self.thread.set_best_answer(self.user, self.post)
1✔
516
        self.thread.save()
1✔
517

518
        response = self.patch(
1✔
519
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
520
        )
521
        self.assertEqual(response.status_code, 400)
1✔
522
        self.assertEqual(
1✔
523
            response.json(),
524
            {
525
                "id": self.post.id,
526
                "detail": [
527
                    "You can't hide this post because its marked as best answer."
528
                ],
529
            },
530
        )
531

532

533
class PostUnhideApiTests(ThreadPostPatchApiTestCase):
1✔
534
    @patch_category_acl({"can_hide_posts": 1})
1✔
535
    def test_show_post(self):
1✔
536
        """api makes it possible to unhide post"""
537
        self.post.is_hidden = True
1✔
538
        self.post.save()
1✔
539

540
        self.post.refresh_from_db()
1✔
541
        self.assertTrue(self.post.is_hidden)
1✔
542

543
        response = self.patch(
1✔
544
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
545
        )
546
        self.assertEqual(response.status_code, 200)
1✔
547

548
        reponse_json = response.json()
1✔
549
        self.assertFalse(reponse_json["is_hidden"])
1✔
550

551
        self.post.refresh_from_db()
1✔
552
        self.assertFalse(self.post.is_hidden)
1✔
553

554
    @patch_category_acl({"can_hide_own_posts": 1})
1✔
555
    def test_show_own_post(self):
1✔
556
        """api makes it possible to unhide owned post"""
557
        self.post.is_hidden = True
1✔
558
        self.post.save()
1✔
559

560
        self.post.refresh_from_db()
1✔
561
        self.assertTrue(self.post.is_hidden)
1✔
562

563
        response = self.patch(
1✔
564
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
565
        )
566
        self.assertEqual(response.status_code, 200)
1✔
567

568
        reponse_json = response.json()
1✔
569
        self.assertFalse(reponse_json["is_hidden"])
1✔
570

571
        self.post.refresh_from_db()
1✔
572
        self.assertFalse(self.post.is_hidden)
1✔
573

574
    @patch_category_acl({"can_hide_posts": 0})
1✔
575
    def test_show_post_no_permission(self):
1✔
576
        """api unhide post with no permission fails"""
577
        self.post.is_hidden = True
1✔
578
        self.post.save()
1✔
579

580
        self.post.refresh_from_db()
1✔
581
        self.assertTrue(self.post.is_hidden)
1✔
582

583
        response = self.patch(
1✔
584
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
585
        )
586
        self.assertEqual(response.status_code, 400)
1✔
587

588
        response_json = response.json()
1✔
589
        self.assertEqual(
1✔
590
            response_json["detail"][0], "You can't reveal posts in this category."
591
        )
592

593
        self.post.refresh_from_db()
1✔
594
        self.assertTrue(self.post.is_hidden)
1✔
595

596
    @patch_category_acl({"can_protect_posts": 0, "can_hide_own_posts": 1})
1✔
597
    def test_show_own_protected_post(self):
1✔
598
        """api validates if we are trying to reveal protected post"""
599
        self.post.is_hidden = True
1✔
600
        self.post.save()
1✔
601

602
        self.post.is_protected = True
1✔
603
        self.post.save()
1✔
604

605
        response = self.patch(
1✔
606
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
607
        )
608
        self.assertEqual(response.status_code, 400)
1✔
609

610
        response_json = response.json()
1✔
611
        self.assertEqual(
1✔
612
            response_json["detail"][0], "This post is protected. You can't reveal it."
613
        )
614

615
        self.post.refresh_from_db()
1✔
616
        self.assertTrue(self.post.is_hidden)
1✔
617

618
    @patch_category_acl({"can_hide_own_posts": 1})
1✔
619
    def test_show_other_user_post(self):
1✔
620
        """api validates post ownership when revealing"""
621
        self.post.is_hidden = True
1✔
622
        self.post.poster = None
1✔
623
        self.post.save()
1✔
624

625
        response = self.patch(
1✔
626
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
627
        )
628
        self.assertEqual(response.status_code, 400)
1✔
629

630
        response_json = response.json()
1✔
631
        self.assertEqual(
1✔
632
            response_json["detail"][0],
633
            "You can't reveal other users posts in this category.",
634
        )
635

636
        self.post.refresh_from_db()
1✔
637
        self.assertTrue(self.post.is_hidden)
1✔
638

639
    @patch_category_acl({"post_edit_time": 1, "can_hide_own_posts": 1})
1✔
640
    def test_show_own_post_after_edit_time(self):
1✔
641
        """api validates if we are trying to reveal post after edit time"""
642
        self.post.is_hidden = True
1✔
643
        self.post.posted_on = timezone.now() - timedelta(minutes=10)
1✔
644
        self.post.save()
1✔
645

646
        response = self.patch(
1✔
647
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
648
        )
649
        self.assertEqual(response.status_code, 400)
1✔
650

651
        response_json = response.json()
1✔
652
        self.assertEqual(
1✔
653
            response_json["detail"][0],
654
            "You can't reveal posts that are older than 1 minute.",
655
        )
656

657
        self.post.refresh_from_db()
1✔
658
        self.assertTrue(self.post.is_hidden)
1✔
659

660
    @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": 1})
1✔
661
    def test_show_post_in_closed_thread(self):
1✔
662
        """api validates if we are trying to reveal post in closed thread"""
663
        self.thread.is_closed = True
1✔
664
        self.thread.save()
1✔
665

666
        self.post.is_hidden = True
1✔
667
        self.post.save()
1✔
668

669
        response = self.patch(
1✔
670
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
671
        )
672
        self.assertEqual(response.status_code, 400)
1✔
673

674
        response_json = response.json()
1✔
675
        self.assertEqual(
1✔
676
            response_json["detail"][0],
677
            "This thread is closed. You can't reveal posts in it.",
678
        )
679

680
        self.post.refresh_from_db()
1✔
681
        self.assertTrue(self.post.is_hidden)
1✔
682

683
    @patch_category_acl({"can_close_threads": False, "can_hide_own_posts": 1})
1✔
684
    def test_show_post_in_closed_category(self):
1✔
685
        """api validates if we are trying to reveal post in closed category"""
686
        self.category.is_closed = True
1✔
687
        self.category.save()
1✔
688

689
        self.post.is_hidden = True
1✔
690
        self.post.save()
1✔
691

692
        response = self.patch(
1✔
693
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
694
        )
695
        self.assertEqual(response.status_code, 400)
1✔
696

697
        response_json = response.json()
1✔
698
        self.assertEqual(
1✔
699
            response_json["detail"][0],
700
            "This category is closed. You can't reveal posts in it.",
701
        )
702

703
        self.post.refresh_from_db()
1✔
704
        self.assertTrue(self.post.is_hidden)
1✔
705

706
    @patch_category_acl({"can_hide_posts": 1})
1✔
707
    def test_show_first_post(self):
1✔
708
        """api unhide first post fails"""
709
        self.thread.set_first_post(self.post)
1✔
710
        self.thread.save()
1✔
711

712
        response = self.patch(
1✔
713
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
714
        )
715
        self.assertEqual(response.status_code, 400)
1✔
716

717
        response_json = response.json()
1✔
718
        self.assertEqual(
1✔
719
            response_json["detail"][0],
720
            "Thread's first post can only be revealed using the reveal thread option.",
721
        )
722

723

724
class PostLikeApiTests(ThreadPostPatchApiTestCase):
1✔
725
    @patch_category_acl({"can_see_posts_likes": 0})
1✔
726
    def test_like_no_see_permission(self):
1✔
727
        """api validates user's permission to see posts likes"""
728
        response = self.patch(
1✔
729
            self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
730
        )
731
        self.assertEqual(response.status_code, 400)
1✔
732
        self.assertEqual(
1✔
733
            response.json(),
734
            {"id": self.post.id, "detail": ["You can't like posts in this category."]},
735
        )
736

737
    @patch_category_acl({"can_like_posts": False})
1✔
738
    def test_like_no_like_permission(self):
1✔
739
        """api validates user's permission to see posts likes"""
740
        response = self.patch(
1✔
741
            self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
742
        )
743
        self.assertEqual(response.status_code, 400)
1✔
744
        self.assertEqual(
1✔
745
            response.json(),
746
            {"id": self.post.id, "detail": ["You can't like posts in this category."]},
747
        )
748

749
    def test_like_post(self):
1✔
750
        """api adds user like to post"""
751
        response = self.patch(
1✔
752
            self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
753
        )
754
        self.assertEqual(response.status_code, 200)
1✔
755

756
        response_json = response.json()
1✔
757
        self.assertEqual(response_json["likes"], 1)
1✔
758
        self.assertEqual(response_json["is_liked"], True)
1✔
759
        self.assertEqual(
1✔
760
            response_json["last_likes"],
761
            [{"id": self.user.id, "username": self.user.username}],
762
        )
763

764
        post = Post.objects.get(pk=self.post.pk)
1✔
765
        self.assertEqual(post.likes, response_json["likes"])
1✔
766
        self.assertEqual(post.last_likes, response_json["last_likes"])
1✔
767

768
    def test_like_liked_post(self):
1✔
769
        """api adds user like to post"""
770
        test.like_post(self.post, username="Myo")
1✔
771
        test.like_post(self.post, username="Mugi")
1✔
772
        test.like_post(self.post, username="Bob")
1✔
773
        test.like_post(self.post, username="Miku")
1✔
774

775
        response = self.patch(
1✔
776
            self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
777
        )
778
        self.assertEqual(response.status_code, 200)
1✔
779

780
        response_json = response.json()
1✔
781
        self.assertEqual(response_json["likes"], 5)
1✔
782
        self.assertEqual(response_json["is_liked"], True)
1✔
783
        self.assertEqual(
1✔
784
            response_json["last_likes"],
785
            [
786
                {"id": self.user.id, "username": self.user.username},
787
                {"id": None, "username": "Miku"},
788
                {"id": None, "username": "Bob"},
789
                {"id": None, "username": "Mugi"},
790
            ],
791
        )
792

793
        post = Post.objects.get(pk=self.post.pk)
1✔
794
        self.assertEqual(post.likes, response_json["likes"])
1✔
795
        self.assertEqual(post.last_likes, response_json["last_likes"])
1✔
796

797
    def test_unlike_post(self):
1✔
798
        """api removes user like from post"""
799
        test.like_post(self.post, self.user)
1✔
800

801
        response = self.patch(
1✔
802
            self.api_link, [{"op": "replace", "path": "is-liked", "value": False}]
803
        )
804
        self.assertEqual(response.status_code, 200)
1✔
805

806
        response_json = response.json()
1✔
807
        self.assertEqual(response_json["likes"], 0)
1✔
808
        self.assertEqual(response_json["is_liked"], False)
1✔
809
        self.assertEqual(response_json["last_likes"], [])
1✔
810

811
        post = Post.objects.get(pk=self.post.pk)
1✔
812
        self.assertEqual(post.likes, response_json["likes"])
1✔
813
        self.assertEqual(post.last_likes, response_json["last_likes"])
1✔
814

815
    def test_like_post_no_change(self):
1✔
816
        """api does no state change if we are linking liked post"""
817
        test.like_post(self.post, self.user)
1✔
818

819
        response = self.patch(
1✔
820
            self.api_link, [{"op": "replace", "path": "is-liked", "value": True}]
821
        )
822
        self.assertEqual(response.status_code, 200)
1✔
823

824
        response_json = response.json()
1✔
825
        self.assertEqual(response_json["likes"], 1)
1✔
826
        self.assertEqual(response_json["is_liked"], True)
1✔
827
        self.assertEqual(
1✔
828
            response_json["last_likes"],
829
            [{"id": self.user.id, "username": self.user.username}],
830
        )
831

832
        post = Post.objects.get(pk=self.post.pk)
1✔
833
        self.assertEqual(post.likes, response_json["likes"])
1✔
834
        self.assertEqual(post.last_likes, response_json["last_likes"])
1✔
835

836
    def test_unlike_post_no_change(self):
1✔
837
        """api does no state change if we are unlinking unliked post"""
838
        response = self.patch(
1✔
839
            self.api_link, [{"op": "replace", "path": "is-liked", "value": False}]
840
        )
841
        self.assertEqual(response.status_code, 200)
1✔
842

843
        response_json = response.json()
1✔
844
        self.assertEqual(response_json["likes"], 0)
1✔
845
        self.assertEqual(response_json["is_liked"], False)
1✔
846
        self.assertEqual(response_json["last_likes"], [])
1✔
847

848

849
class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
1✔
850
    def setUp(self):
1✔
851
        super().setUp()
1✔
852

853
        self.event = test.reply_thread(self.thread, poster=self.user, is_event=True)
1✔
854

855
        self.api_link = reverse(
1✔
856
            "misago:api:thread-post-detail",
857
            kwargs={"thread_pk": self.thread.pk, "pk": self.event.pk},
858
        )
859

860
    def refresh_event(self):
1✔
UNCOV
861
        self.event = self.thread.post_set.get(pk=self.event.pk)
×
862

863

864
class EventAnonPatchApiTests(ThreadEventPatchApiTestCase):
1✔
865
    def test_anonymous_user(self):
1✔
866
        """anonymous users can't change event state"""
867
        self.logout_user()
1✔
868

869
        response = self.patch(
1✔
870
            self.api_link, [{"op": "add", "path": "acl", "value": True}]
871
        )
872
        self.assertEqual(response.status_code, 403)
1✔
873

874

875
class EventAddAclApiTests(ThreadEventPatchApiTestCase):
1✔
876
    def test_add_acl_true(self):
1✔
877
        """api adds current event's acl to response"""
878
        response = self.patch(
1✔
879
            self.api_link, [{"op": "add", "path": "acl", "value": True}]
880
        )
881
        self.assertEqual(response.status_code, 200)
1✔
882

883
        response_json = response.json()
1✔
884
        self.assertTrue(response_json["acl"])
1✔
885

886
    def test_add_acl_false(self):
1✔
887
        """
888
        if value is false, api won't add acl to the response, but will set empty key
889
        """
890
        response = self.patch(
1✔
891
            self.api_link, [{"op": "add", "path": "acl", "value": False}]
892
        )
893
        self.assertEqual(response.status_code, 200)
1✔
894

895
        response_json = response.json()
1✔
896
        self.assertIsNone(response_json["acl"])
1✔
897

898
        response = self.patch(
1✔
899
            self.api_link, [{"op": "add", "path": "acl", "value": True}]
900
        )
901
        self.assertEqual(response.status_code, 200)
1✔
902

903

904
class EventHideApiTests(ThreadEventPatchApiTestCase):
1✔
905
    @patch_category_acl({"can_hide_events": 1})
1✔
906
    def test_hide_event(self):
1✔
907
        """api makes it possible to hide event"""
908
        response = self.patch(
1✔
909
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
910
        )
911
        self.assertEqual(response.status_code, 200)
1✔
912

913
        self.event.refresh_from_db()
1✔
914
        self.assertTrue(self.event.is_hidden)
1✔
915

916
    @patch_category_acl({"can_hide_events": 1})
1✔
917
    def test_show_event(self):
1✔
918
        """api makes it possible to unhide event"""
919
        self.event.is_hidden = True
1✔
920
        self.event.save()
1✔
921

922
        self.event.refresh_from_db()
1✔
923
        self.assertTrue(self.event.is_hidden)
1✔
924

925
        response = self.patch(
1✔
926
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
927
        )
928
        self.assertEqual(response.status_code, 200)
1✔
929

930
        self.event.refresh_from_db()
1✔
931
        self.assertFalse(self.event.is_hidden)
1✔
932

933
    @patch_category_acl({"can_hide_events": 0})
1✔
934
    def test_hide_event_no_permission(self):
1✔
935
        """api hide event with no permission fails"""
936
        response = self.patch(
1✔
937
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
938
        )
939
        self.assertEqual(response.status_code, 400)
1✔
940

941
        response_json = response.json()
1✔
942
        self.assertEqual(
1✔
943
            response_json["detail"][0], "You can't hide events in this category."
944
        )
945

946
        self.event.refresh_from_db()
1✔
947
        self.assertFalse(self.event.is_hidden)
1✔
948

949
    @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
1✔
950
    def test_hide_event_closed_thread_no_permission(self):
1✔
951
        """api hide event in closed thread with no permission fails"""
952
        self.thread.is_closed = True
1✔
953
        self.thread.save()
1✔
954

955
        response = self.patch(
1✔
956
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
957
        )
958
        self.assertEqual(response.status_code, 400)
1✔
959

960
        response_json = response.json()
1✔
961
        self.assertEqual(
1✔
962
            response_json["detail"][0],
963
            "This thread is closed. You can't hide events in it.",
964
        )
965

966
        self.event.refresh_from_db()
1✔
967
        self.assertFalse(self.event.is_hidden)
1✔
968

969
    @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
1✔
970
    def test_hide_event_closed_category_no_permission(self):
1✔
971
        """api hide event in closed category with no permission fails"""
972
        self.category.is_closed = True
1✔
973
        self.category.save()
1✔
974

975
        response = self.patch(
1✔
976
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": True}]
977
        )
978
        self.assertEqual(response.status_code, 400)
1✔
979

980
        response_json = response.json()
1✔
981
        self.assertEqual(
1✔
982
            response_json["detail"][0],
983
            "This category is closed. You can't hide events in it.",
984
        )
985

986
        self.event.refresh_from_db()
1✔
987
        self.assertFalse(self.event.is_hidden)
1✔
988

989
    @patch_category_acl({"can_hide_events": 0})
1✔
990
    def test_show_event_no_permission(self):
1✔
991
        """api unhide event with no permission fails"""
992
        self.event.is_hidden = True
1✔
993
        self.event.save()
1✔
994

995
        self.event.refresh_from_db()
1✔
996
        self.assertTrue(self.event.is_hidden)
1✔
997

998
        response = self.patch(
1✔
999
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
1000
        )
1001
        self.assertEqual(response.status_code, 404)
1✔
1002

1003
    @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
1✔
1004
    def test_show_event_closed_thread_no_permission(self):
1✔
1005
        """api show event in closed thread with no permission fails"""
1006
        self.event.is_hidden = True
1✔
1007
        self.event.save()
1✔
1008

1009
        self.thread.is_closed = True
1✔
1010
        self.thread.save()
1✔
1011

1012
        response = self.patch(
1✔
1013
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
1014
        )
1015
        self.assertEqual(response.status_code, 400)
1✔
1016

1017
        response_json = response.json()
1✔
1018
        self.assertEqual(
1✔
1019
            response_json["detail"][0],
1020
            "This thread is closed. You can't reveal events in it.",
1021
        )
1022

1023
        self.event.refresh_from_db()
1✔
1024
        self.assertTrue(self.event.is_hidden)
1✔
1025

1026
    @patch_category_acl({"can_close_threads": False, "can_hide_events": 1})
1✔
1027
    def test_show_event_closed_category_no_permission(self):
1✔
1028
        """api show event in closed category with no permission fails"""
1029
        self.event.is_hidden = True
1✔
1030
        self.event.save()
1✔
1031

1032
        self.category.is_closed = True
1✔
1033
        self.category.save()
1✔
1034

1035
        response = self.patch(
1✔
1036
            self.api_link, [{"op": "replace", "path": "is-hidden", "value": False}]
1037
        )
1038
        self.assertEqual(response.status_code, 400)
1✔
1039

1040
        response_json = response.json()
1✔
1041
        self.assertEqual(
1✔
1042
            response_json["detail"][0],
1043
            "This category is closed. You can't reveal events in it.",
1044
        )
1045

1046
        self.event.refresh_from_db()
1✔
1047
        self.assertTrue(self.event.is_hidden)
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

© 2025 Coveralls, Inc