|
@@ -2,12 +2,12 @@ import json
|
|
|
|
|
|
from django.urls import reverse
|
|
|
|
|
|
-from misago.acl.testutils import override_acl
|
|
|
from misago.categories.models import Category
|
|
|
from misago.readtracker import poststracker
|
|
|
from misago.threads import testutils
|
|
|
from misago.threads.models import Post, Thread
|
|
|
from misago.threads.serializers.moderation import POSTS_LIMIT
|
|
|
+from misago.threads.test import patch_category_acl, patch_other_category_acl
|
|
|
from misago.users.testutils import AuthenticatedUserTestCase
|
|
|
|
|
|
|
|
@@ -29,66 +29,14 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
)
|
|
|
|
|
|
Category(
|
|
|
- name='Category B',
|
|
|
- slug='category-b',
|
|
|
+ name='Other category',
|
|
|
+ slug='other-category',
|
|
|
).insert_at(
|
|
|
self.category,
|
|
|
position='last-child',
|
|
|
save=True,
|
|
|
)
|
|
|
- self.category_b = Category.objects.get(slug='category-b')
|
|
|
-
|
|
|
- self.override_acl()
|
|
|
- self.override_other_acl()
|
|
|
-
|
|
|
- def refresh_thread(self):
|
|
|
- self.thread = Thread.objects.get(pk=self.thread.pk)
|
|
|
-
|
|
|
- def override_acl(self, extra_acl=None):
|
|
|
- new_acl = self.user.acl_cache
|
|
|
- new_acl['categories'][self.category.pk].update({
|
|
|
- 'can_see': 1,
|
|
|
- 'can_browse': 1,
|
|
|
- 'can_start_threads': 1,
|
|
|
- 'can_reply_threads': 1,
|
|
|
- 'can_edit_posts': 1,
|
|
|
- 'can_approve_content': 0,
|
|
|
- 'can_move_posts': 1,
|
|
|
- })
|
|
|
-
|
|
|
- if extra_acl:
|
|
|
- new_acl['categories'][self.category.pk].update(extra_acl)
|
|
|
-
|
|
|
- override_acl(self.user, new_acl)
|
|
|
-
|
|
|
- def override_other_acl(self, acl=None):
|
|
|
- other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
|
|
|
- other_category_acl.update({
|
|
|
- 'can_see': 1,
|
|
|
- 'can_browse': 1,
|
|
|
- 'can_start_threads': 0,
|
|
|
- 'can_reply_threads': 0,
|
|
|
- 'can_edit_posts': 1,
|
|
|
- 'can_approve_content': 0,
|
|
|
- 'can_move_posts': 1,
|
|
|
- })
|
|
|
-
|
|
|
- if acl:
|
|
|
- other_category_acl.update(acl)
|
|
|
-
|
|
|
- categories_acl = self.user.acl_cache['categories']
|
|
|
- categories_acl[self.category_b.pk] = other_category_acl
|
|
|
-
|
|
|
- visible_categories = [self.category.pk]
|
|
|
- if other_category_acl['can_see']:
|
|
|
- visible_categories.append(self.category_b.pk)
|
|
|
-
|
|
|
- override_acl(
|
|
|
- self.user, {
|
|
|
- 'visible_categories': visible_categories,
|
|
|
- 'categories': categories_acl,
|
|
|
- }
|
|
|
- )
|
|
|
+ self.other_category = Category.objects.get(slug='other-category')
|
|
|
|
|
|
def test_anonymous_user(self):
|
|
|
"""you need to authenticate to split posts"""
|
|
@@ -100,16 +48,16 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "This action is not available to guests.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": False})
|
|
|
def test_no_permission(self):
|
|
|
"""api validates permission to split"""
|
|
|
- self.override_acl({'can_move_posts': 0})
|
|
|
-
|
|
|
response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
|
|
|
self.assertEqual(response.status_code, 403)
|
|
|
self.assertEqual(response.json(), {
|
|
|
"detail": "You can't split posts from this thread.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_empty_data(self):
|
|
|
"""api handles empty data"""
|
|
|
response = self.client.post(self.api_link)
|
|
@@ -118,36 +66,34 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "You have to specify at least one post to split.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_invalid_data(self):
|
|
|
"""api handles post that is invalid type"""
|
|
|
- self.override_acl()
|
|
|
response = self.client.post(self.api_link, '[]', content_type="application/json")
|
|
|
self.assertEqual(response.status_code, 400)
|
|
|
self.assertEqual(response.json(), {
|
|
|
"non_field_errors": ["Invalid data. Expected a dictionary, but got list."],
|
|
|
})
|
|
|
|
|
|
- self.override_acl()
|
|
|
response = self.client.post(self.api_link, '123', content_type="application/json")
|
|
|
self.assertEqual(response.status_code, 400)
|
|
|
self.assertEqual(response.json(), {
|
|
|
"non_field_errors": ["Invalid data. Expected a dictionary, but got int."],
|
|
|
})
|
|
|
|
|
|
- self.override_acl()
|
|
|
response = self.client.post(self.api_link, '"string"', content_type="application/json")
|
|
|
self.assertEqual(response.status_code, 400)
|
|
|
self.assertEqual(response.json(), {
|
|
|
"non_field_errors": ["Invalid data. Expected a dictionary, but got str."],
|
|
|
})
|
|
|
|
|
|
- self.override_acl()
|
|
|
response = self.client.post(self.api_link, 'malformed', content_type="application/json")
|
|
|
self.assertEqual(response.status_code, 400)
|
|
|
self.assertEqual(response.json(), {
|
|
|
"detail": "JSON parse error - Expecting value: line 1 column 1 (char 0)",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_no_posts_ids(self):
|
|
|
"""api rejects no posts ids"""
|
|
|
response = self.client.post(
|
|
@@ -159,6 +105,8 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.assertEqual(response.json(), {
|
|
|
"detail": "You have to specify at least one post to split.",
|
|
|
})
|
|
|
+
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_empty_posts_ids(self):
|
|
|
"""api rejects empty posts ids list"""
|
|
|
response = self.client.post(
|
|
@@ -173,6 +121,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "You have to specify at least one post to split.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_invalid_posts_data(self):
|
|
|
"""api handles invalid data"""
|
|
|
response = self.client.post(
|
|
@@ -187,6 +136,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": 'Expected a list of items but got type "str".',
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_invalid_posts_ids(self):
|
|
|
"""api handles invalid post id"""
|
|
|
response = self.client.post(
|
|
@@ -201,6 +151,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "One or more post ids received were invalid.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_limit(self):
|
|
|
"""api rejects more posts than split limit"""
|
|
|
response = self.client.post(
|
|
@@ -215,6 +166,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "No more than %s posts can be split at single time." % POSTS_LIMIT,
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_invisible(self):
|
|
|
"""api validates posts visibility"""
|
|
|
response = self.client.post(
|
|
@@ -229,6 +181,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "One or more posts to split could not be found.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_event(self):
|
|
|
"""api rejects events split"""
|
|
|
response = self.client.post(
|
|
@@ -243,6 +196,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "Events can't be split.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_first_post(self):
|
|
|
"""api rejects first post split"""
|
|
|
response = self.client.post(
|
|
@@ -257,6 +211,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "You can't split thread's first post.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_hidden_posts(self):
|
|
|
"""api recjects attempt to split urneadable hidden post"""
|
|
|
response = self.client.post(
|
|
@@ -271,13 +226,12 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "You can't split posts the content you can't see.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_close_threads": False})
|
|
|
def test_split_posts_closed_thread_no_permission(self):
|
|
|
"""api recjects attempt to split posts from closed thread"""
|
|
|
self.thread.is_closed = True
|
|
|
self.thread.save()
|
|
|
|
|
|
- self.override_acl({'can_close_threads': 0})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -290,13 +244,12 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "This thread is closed. You can't split posts in it.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_close_threads": False})
|
|
|
def test_split_posts_closed_category_no_permission(self):
|
|
|
"""api recjects attempt to split posts from closed thread"""
|
|
|
self.category.is_closed = True
|
|
|
self.category.save()
|
|
|
|
|
|
- self.override_acl({'can_close_threads': 0})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -309,6 +262,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "This category is closed. You can't split posts in it.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_other_thread_posts(self):
|
|
|
"""api recjects attempt to split other thread's post"""
|
|
|
other_thread = testutils.post_thread(self.category)
|
|
@@ -325,6 +279,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
"detail": "One or more posts to split could not be found.",
|
|
|
})
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_empty_new_thread_data(self):
|
|
|
"""api handles empty form data"""
|
|
|
response = self.client.post(
|
|
@@ -344,6 +299,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_invalid_final_title(self):
|
|
|
"""api rejects split because final thread title was invalid"""
|
|
|
response = self.client.post(
|
|
@@ -364,16 +320,16 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_other_category_acl({"can_see": False})
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_invalid_category(self):
|
|
|
"""api rejects split because final category was invalid"""
|
|
|
- self.override_other_acl({'can_see': 0})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
|
'posts': self.posts,
|
|
|
'title': 'Valid thread title',
|
|
|
- 'category': self.category_b.id,
|
|
|
+ 'category': self.other_category.id,
|
|
|
}),
|
|
|
content_type="application/json",
|
|
|
)
|
|
@@ -386,10 +342,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_start_threads": False})
|
|
|
def test_split_unallowed_start_thread(self):
|
|
|
"""api rejects split because category isn't allowing starting threads"""
|
|
|
- self.override_acl({'can_start_threads': 0})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -408,6 +363,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_invalid_weight(self):
|
|
|
"""api rejects split because final weight was invalid"""
|
|
|
response = self.client.post(
|
|
@@ -429,6 +385,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_unallowed_global_weight(self):
|
|
|
"""api rejects split because global weight was unallowed"""
|
|
|
response = self.client.post(
|
|
@@ -450,6 +407,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_pin_threads": 0})
|
|
|
def test_split_unallowed_local_weight(self):
|
|
|
"""api rejects split because local weight was unallowed"""
|
|
|
response = self.client.post(
|
|
@@ -471,10 +429,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_pin_threads": 1})
|
|
|
def test_split_allowed_local_weight(self):
|
|
|
"""api allows local weight"""
|
|
|
- self.override_acl({'can_pin_threads': 1})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -494,10 +451,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_pin_threads": 2})
|
|
|
def test_split_allowed_global_weight(self):
|
|
|
"""api allows global weight"""
|
|
|
- self.override_acl({'can_pin_threads': 2})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -517,6 +473,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_close_threads": False})
|
|
|
def test_split_unallowed_close(self):
|
|
|
"""api rejects split because closing thread was unallowed"""
|
|
|
response = self.client.post(
|
|
@@ -538,10 +495,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_close_threads": True})
|
|
|
def test_split_with_close(self):
|
|
|
"""api allows for closing thread"""
|
|
|
- self.override_acl({'can_close_threads': True})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -562,6 +518,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_hide_threads": 0})
|
|
|
def test_split_unallowed_hidden(self):
|
|
|
"""api rejects split because hidden thread was unallowed"""
|
|
|
response = self.client.post(
|
|
@@ -583,10 +540,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True, "can_hide_threads": 1})
|
|
|
def test_split_with_hide(self):
|
|
|
"""api allows for hiding thread"""
|
|
|
- self.override_acl({'can_hide_threads': True})
|
|
|
-
|
|
|
response = self.client.post(
|
|
|
self.api_link,
|
|
|
json.dumps({
|
|
@@ -607,9 +563,10 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
}
|
|
|
)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split(self):
|
|
|
"""api splits posts to new thread"""
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.replies, 2)
|
|
|
|
|
|
response = self.client.post(
|
|
@@ -628,12 +585,13 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.assertEqual(split_thread.replies, 1)
|
|
|
|
|
|
# posts were removed from old thread
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.replies, 0)
|
|
|
|
|
|
# posts were moved to new thread
|
|
|
self.assertEqual(split_thread.post_set.filter(pk__in=self.posts).count(), 2)
|
|
|
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_best_answer(self):
|
|
|
"""api splits best answer to new thread"""
|
|
|
best_answer = testutils.reply_thread(self.thread)
|
|
@@ -642,7 +600,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.thread.synchronize()
|
|
|
self.thread.save()
|
|
|
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.best_answer, best_answer)
|
|
|
self.assertEqual(self.thread.replies, 3)
|
|
|
|
|
@@ -658,7 +616,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
# best_answer was moved and unmarked
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.replies, 2)
|
|
|
self.assertIsNone(self.thread.best_answer)
|
|
|
|
|
@@ -666,18 +624,18 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.assertEqual(split_thread.replies, 0)
|
|
|
self.assertIsNone(split_thread.best_answer)
|
|
|
|
|
|
+ @patch_other_category_acl({
|
|
|
+ 'can_start_threads': True,
|
|
|
+ 'can_close_threads': True,
|
|
|
+ 'can_hide_threads': True,
|
|
|
+ 'can_pin_threads': 2,
|
|
|
+ })
|
|
|
+ @patch_category_acl({"can_move_posts": True})
|
|
|
def test_split_kitchensink(self):
|
|
|
"""api splits posts with kitchensink"""
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.replies, 2)
|
|
|
|
|
|
- self.override_other_acl({
|
|
|
- 'can_start_threads': 2,
|
|
|
- 'can_close_threads': True,
|
|
|
- 'can_hide_threads': True,
|
|
|
- 'can_pin_threads': 2,
|
|
|
- })
|
|
|
-
|
|
|
poststracker.save_read(self.user, self.thread.first_post)
|
|
|
for post in self.posts:
|
|
|
poststracker.save_read(self.user, Post.objects.select_related().get(pk=post))
|
|
@@ -687,7 +645,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
json.dumps({
|
|
|
'posts': self.posts,
|
|
|
'title': 'Split thread',
|
|
|
- 'category': self.category_b.id,
|
|
|
+ 'category': self.other_category.id,
|
|
|
'weight': 2,
|
|
|
'is_closed': 1,
|
|
|
'is_hidden': 1,
|
|
@@ -697,14 +655,14 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
# thread was created
|
|
|
- split_thread = self.category_b.thread_set.get(slug='split-thread')
|
|
|
+ split_thread = self.other_category.thread_set.get(slug='split-thread')
|
|
|
self.assertEqual(split_thread.replies, 1)
|
|
|
self.assertEqual(split_thread.weight, 2)
|
|
|
self.assertTrue(split_thread.is_closed)
|
|
|
self.assertTrue(split_thread.is_hidden)
|
|
|
|
|
|
# posts were removed from old thread
|
|
|
- self.refresh_thread()
|
|
|
+ self.thread.refresh_from_db()
|
|
|
self.assertEqual(self.thread.replies, 0)
|
|
|
|
|
|
# posts were moved to new thread
|