Browse Source

Remove remaining assertContains from API tests

Rafał Pitoń 7 years ago
parent
commit
38684c1cba

+ 28 - 15
misago/threads/tests/test_thread_reply_api.py

@@ -44,7 +44,7 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.json(), {
         self.assertEqual(response.json(), {
-            'detail': "You can't reply to threads in this category.",
+            'detail': "This action is not available to guests.",
         })
         })
 
 
     def test_thread_visibility(self):
     def test_thread_visibility(self):
@@ -52,14 +52,23 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.override_acl({'can_see': 0})
         self.override_acl({'can_see': 0})
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(response.json(), {
+            'detail': "NOT FOUND",
+        })
 
 
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(response.json(), {
+            'detail': "NOT FOUND",
+        })
 
 
         self.override_acl({'can_see_all_threads': 0})
         self.override_acl({'can_see_all_threads': 0})
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(response.json(), {
+            'detail': "NOT FOUND",
+        })
 
 
     def test_cant_reply_thread(self):
     def test_cant_reply_thread(self):
         """permission to reply thread is validated"""
         """permission to reply thread is validated"""
@@ -79,11 +88,10 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.category.save()
         self.category.save()
 
 
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
-        self.assertContains(
-            response,
-            "This category is closed. You can't reply to threads in it.",
-            status_code=403
-        )
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "This category is closed. You can't reply to threads in it.",
+        })
 
 
         # allow to post in closed category
         # allow to post in closed category
         self.override_acl({'can_close_threads': 1})
         self.override_acl({'can_close_threads': 1})
@@ -99,9 +107,10 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
-        self.assertContains(
-            response, "You can't reply to closed threads in this category.", status_code=403
-        )
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "You can't reply to closed threads in this category.",
+        })
 
 
         # allow to post in closed thread
         # allow to post in closed thread
         self.override_acl({'can_close_threads': 1})
         self.override_acl({'can_close_threads': 1})
@@ -114,9 +123,11 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.override_acl()
         self.override_acl()
 
 
         response = self.client.post(self.api_link, data={})
         response = self.client.post(self.api_link, data={})
-
-        self.assertContains(response, "You have to enter a message.", status_code=400)
-
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'post': ['You have to enter a message.'],
+        })
+        
     def test_invalid_data(self):
     def test_invalid_data(self):
         """api errors for invalid request data"""
         """api errors for invalid request data"""
         self.override_acl()
         self.override_acl()
@@ -126,8 +137,10 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_post_is_validated(self):
     def test_post_is_validated(self):
         """post is validated"""
         """post is validated"""
@@ -155,7 +168,7 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
-
+        
         thread = Thread.objects.get(pk=self.thread.pk)
         thread = Thread.objects.get(pk=self.thread.pk)
 
 
         self.override_acl()
         self.override_acl()

+ 48 - 21
misago/threads/tests/test_thread_start_api.py

@@ -45,6 +45,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "This action is not available to guests.",
+        })
 
 
     def test_cant_see(self):
     def test_cant_see(self):
         """has no permission to see selected category"""
         """has no permission to see selected category"""
@@ -53,9 +56,13 @@ class StartThreadTests(AuthenticatedUserTestCase):
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'category': self.category.pk,
             'category': self.category.pk,
         })
         })
-
-        self.assertContains(response, "Selected category is invalid.", status_code=400)
-
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'category': ["Selected category is invalid."],
+            'title': ["You have to enter thread title."],
+            'post': ["You have to enter a message."],
+        })
+        
     def test_cant_browse(self):
     def test_cant_browse(self):
         """has no permission to browse selected category"""
         """has no permission to browse selected category"""
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
@@ -63,8 +70,12 @@ class StartThreadTests(AuthenticatedUserTestCase):
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'category': self.category.pk,
             'category': self.category.pk,
         })
         })
-
-        self.assertContains(response, "Selected category is invalid.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'category': ["Selected category is invalid."],
+            'title': ["You have to enter thread title."],
+            'post': ["You have to enter a message."],
+        })
 
 
     def test_cant_start_thread(self):
     def test_cant_start_thread(self):
         """permission to start thread in category is validated"""
         """permission to start thread in category is validated"""
@@ -73,12 +84,14 @@ class StartThreadTests(AuthenticatedUserTestCase):
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'category': self.category.pk,
             'category': self.category.pk,
         })
         })
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'category': ["You don't have permission to start new threads in this category."],
+            'title': ["You have to enter thread title."],
+            'post': ["You have to enter a message."],
+        })
 
 
-        self.assertContains(
-            response, "You don't have permission to start new threads", status_code=400
-        )
-
-    def test_cant_start_thread_in_locked_category(self):
+    def test_cant_start_thread_in_closed_category(self):
         """can't post in closed category"""
         """can't post in closed category"""
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
@@ -88,8 +101,12 @@ class StartThreadTests(AuthenticatedUserTestCase):
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'category': self.category.pk,
             'category': self.category.pk,
         })
         })
-
-        self.assertContains(response, "This category is closed.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'category': ["This category is closed. You can't start new threads in it."],
+            'title': ["You have to enter thread title."],
+            'post': ["You have to enter a message."],
+        })
 
 
     def test_cant_start_thread_in_invalid_category(self):
     def test_cant_start_thread_in_invalid_category(self):
         """can't post in invalid category"""
         """can't post in invalid category"""
@@ -98,9 +115,16 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         self.override_acl({'can_close_threads': 0})
         self.override_acl({'can_close_threads': 0})
 
 
-        response = self.client.post(self.api_link, {'category': self.category.pk * 100000})
-
-        self.assertContains(response, "Selected category doesn't exist", status_code=400)
+        response = self.client.post(self.api_link, {'category': self.category.pk * 100})
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            # fixme: invalid category should return same error as invisible one
+            'category': [
+                "Selected category doesn't exist or you don't have permission to browse it."
+            ],
+            'title': ["You have to enter thread title."],
+            'post': ["You have to enter a message."],
+        })
 
 
     def test_empty_data(self):
     def test_empty_data(self):
         """no data sent handling has no showstoppers"""
         """no data sent handling has no showstoppers"""
@@ -125,8 +149,12 @@ class StartThreadTests(AuthenticatedUserTestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(
+            response.json(), {
+                'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+            }
+        )
 
 
     def test_title_is_validated(self):
     def test_title_is_validated(self):
         """title is validated"""
         """title is validated"""
@@ -140,7 +168,6 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'post': "Lorem ipsum dolor met, sit amet elit!",
                 'post': "Lorem ipsum dolor met, sit amet elit!",
             }
             }
         )
         )
-
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(), {
             response.json(), {
@@ -160,7 +187,6 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'post': "a",
                 'post': "a",
             }
             }
         )
         )
-
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(), {
             response.json(), {
@@ -183,8 +209,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         thread = self.user.thread_set.all()[:1][0]
         thread = self.user.thread_set.all()[:1][0]
 
 
-        response_json = response.json()
-        self.assertEqual(response_json['url'], thread.get_absolute_url())
+        thread_json = response.json()
+        self.assertEqual(thread_json['title'], "Hello, I am test thread!")
+        self.assertEqual(thread_json['url'], thread.get_absolute_url())
 
 
         self.override_acl()
         self.override_acl()
         response = self.client.get(thread.get_absolute_url())
         response = self.client.get(thread.get_absolute_url())

+ 154 - 34
misago/threads/tests/test_threads_editor_api.py

@@ -80,21 +80,40 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
         self.logout_user()
         self.logout_user()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "You need to be signed in", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You need to be signed in to start threads.",
+            }
+        )
 
 
     def test_category_visibility_validation(self):
     def test_category_visibility_validation(self):
         """endpoint omits non-browseable categories"""
         """endpoint omits non-browseable categories"""
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "No categories that allow new threads", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': (
+                    "No categories that allow new threads are available to you at the moment."
+                ),
+            }
+        )
 
 
     def test_category_disallowing_new_threads(self):
     def test_category_disallowing_new_threads(self):
         """endpoint omits category disallowing starting threads"""
         """endpoint omits category disallowing starting threads"""
         self.override_acl({'can_start_threads': 0})
         self.override_acl({'can_start_threads': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "No categories that allow new threads", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': (
+                    "No categories that allow new threads are available to you at the moment."
+                ),
+            }
+        )
 
 
     def test_category_closed_disallowing_new_threads(self):
     def test_category_closed_disallowing_new_threads(self):
         """endpoint omits closed category"""
         """endpoint omits closed category"""
@@ -104,7 +123,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
         self.category.save()
         self.category.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "No categories that allow new threads", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': (
+                    "No categories that allow new threads are available to you at the moment."
+                ),
+            }
+        )
 
 
     def test_category_closed_allowing_new_threads(self):
     def test_category_closed_allowing_new_threads(self):
         """endpoint adds closed category that allows new threads"""
         """endpoint adds closed category that allows new threads"""
@@ -271,29 +297,52 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         self.logout_user()
         self.logout_user()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "You have to sign in to reply threads.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You have to sign in to reply threads.",
+            }
+        )
 
 
     def test_thread_visibility(self):
     def test_thread_visibility(self):
         """thread's visibility is validated"""
         """thread's visibility is validated"""
         self.override_acl({'can_see': 0})
         self.override_acl({'can_see': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
         self.override_acl({'can_see_all_threads': 0})
         self.override_acl({'can_see_all_threads': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
     def test_no_reply_permission(self):
     def test_no_reply_permission(self):
         """permssion to reply is validated"""
         """permssion to reply is validated"""
         self.override_acl({'can_reply_threads': 0})
         self.override_acl({'can_reply_threads': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "You can't reply to threads in this category.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't reply to threads in this category.",
+            }
         )
         )
 
 
     def test_closed_category(self):
     def test_closed_category(self):
@@ -304,10 +353,11 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         self.category.save()
         self.category.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response,
-            "This category is closed. You can't reply to threads in it.",
-            status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "This category is closed. You can't reply to threads in it.",
+            }
         )
         )
 
 
         # allow to post in closed category
         # allow to post in closed category
@@ -324,8 +374,11 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "You can't reply to closed threads in this category.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't reply to closed threads in this category.",
+            }
         )
         )
 
 
         # allow to post in closed thread
         # allow to post in closed thread
@@ -341,18 +394,20 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_reply_to_visibility(self):
+    def test_reply_to_invisible_post(self):
         """api validates replied post visibility"""
         """api validates replied post visibility"""
         self.override_acl({'can_reply_threads': 1})
         self.override_acl({'can_reply_threads': 1})
 
 
         # unapproved reply can't be replied to
         # unapproved reply can't be replied to
-        unapproved_reply = testutils.reply_thread(
-            self.thread,
-            is_unapproved=True,
-        )
+        unapproved_reply = testutils.reply_thread(self.thread, is_unapproved=True)
 
 
         response = self.client.get('{}?reply={}'.format(self.api_link, unapproved_reply.pk))
         response = self.client.get('{}?reply={}'.format(self.api_link, unapproved_reply.pk))
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': "No Post matches the given query.",
+            }
+        )
 
 
         # hidden reply can't be replied to
         # hidden reply can't be replied to
         self.override_acl({'can_reply_threads': 1})
         self.override_acl({'can_reply_threads': 1})
@@ -360,7 +415,12 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         hidden_reply = testutils.reply_thread(self.thread, is_hidden=True)
         hidden_reply = testutils.reply_thread(self.thread, is_hidden=True)
 
 
         response = self.client.get('{}?reply={}'.format(self.api_link, hidden_reply.pk))
         response = self.client.get('{}?reply={}'.format(self.api_link, hidden_reply.pk))
-        self.assertContains(response, "You can't reply to hidden posts", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't reply to hidden posts.",
+            }
+        )
 
 
     def test_reply_to_other_thread_post(self):
     def test_reply_to_other_thread_post(self):
         """api validates is replied post belongs to same thread"""
         """api validates is replied post belongs to same thread"""
@@ -369,6 +429,11 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
 
         response = self.client.get('{}?reply={}'.format(self.api_link, reply_to.pk))
         response = self.client.get('{}?reply={}'.format(self.api_link, reply_to.pk))
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': "No Post matches the given query.",
+            }
+        )
 
 
     def test_reply_to_event(self):
     def test_reply_to_event(self):
         """events can't be edited"""
         """events can't be edited"""
@@ -377,8 +442,12 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         reply_to = testutils.reply_thread(self.thread, is_event=True)
         reply_to = testutils.reply_thread(self.thread, is_event=True)
 
 
         response = self.client.get('{}?reply={}'.format(self.api_link, reply_to.pk))
         response = self.client.get('{}?reply={}'.format(self.api_link, reply_to.pk))
-
-        self.assertContains(response, "You can't reply to events.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't reply to events.",
+            }
+        )
 
 
     def test_reply_to(self):
     def test_reply_to(self):
         """api includes replied to post details in response"""
         """api includes replied to post details in response"""
@@ -418,28 +487,53 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.logout_user()
         self.logout_user()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "You have to sign in to edit posts.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You have to sign in to edit posts.",
+            }
+        )
 
 
     def test_thread_visibility(self):
     def test_thread_visibility(self):
         """thread's visibility is validated"""
         """thread's visibility is validated"""
         self.override_acl({'can_see': 0})
         self.override_acl({'can_see': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
         self.override_acl({'can_see_all_threads': 0})
         self.override_acl({'can_see_all_threads': 0})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': 'NOT FOUND',
+            }
+        )
 
 
     def test_no_edit_permission(self):
     def test_no_edit_permission(self):
         """permssion to edit is validated"""
         """permssion to edit is validated"""
         self.override_acl({'can_edit_posts': 0})
         self.override_acl({'can_edit_posts': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "You can't edit posts in this category.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't edit posts in this category.",
+            }
+        )
 
 
     def test_closed_category(self):
     def test_closed_category(self):
         """permssion to edit in closed category is validated"""
         """permssion to edit in closed category is validated"""
@@ -449,8 +543,11 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.category.save()
         self.category.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "This category is closed. You can't edit posts in it.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "This category is closed. You can't edit posts in it.",
+            }
         )
         )
 
 
         # allow to edit in closed category
         # allow to edit in closed category
@@ -467,8 +564,11 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "This thread is closed. You can't edit posts in it.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "This thread is closed. You can't edit posts in it.",
+            }
         )
         )
 
 
         # allow to edit in closed thread
         # allow to edit in closed thread
@@ -485,8 +585,11 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.post.save()
         self.post.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "This post is protected. You can't edit it.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "This post is protected. You can't edit it.",
+            }
         )
         )
 
 
         # allow to post in closed thread
         # allow to post in closed thread
@@ -503,7 +606,12 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.post.save()
         self.post.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(response, "This post is hidden, you can't edit it.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "This post is hidden, you can't edit it.",
+            }
+        )
 
 
         # allow hidden edition
         # allow hidden edition
         self.override_acl({'can_edit_posts': 1, 'can_hide_posts': 1})
         self.override_acl({'can_edit_posts': 1, 'can_hide_posts': 1})
@@ -523,6 +631,11 @@ class EditReplyEditorApiTests(EditorApiTestCase):
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(
+            response.json(), {
+                'detail': "No Post matches the given query.",
+            }
+        )
 
 
         # allow unapproved edition
         # allow unapproved edition
         self.override_acl({'can_edit_posts': 2, 'can_approve_content': 1})
         self.override_acl({'can_edit_posts': 2, 'can_approve_content': 1})
@@ -538,8 +651,12 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.post.save()
         self.post.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-
-        self.assertContains(response, "Events can't be edited.", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "Events can't be edited.",
+            }
+        )
 
 
     def test_other_user_post(self):
     def test_other_user_post(self):
         """api validates if other user's post can be edited"""
         """api validates if other user's post can be edited"""
@@ -549,8 +666,11 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.post.save()
         self.post.save()
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
-        self.assertContains(
-            response, "You can't edit other users posts in this category.", status_code=403
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(
+            response.json(), {
+                'detail': "You can't edit other users posts in this category.",
+            }
         )
         )
 
 
         # allow other users post edition
         # allow other users post edition

+ 1 - 1
misago/users/serializers/register.py

@@ -53,7 +53,7 @@ class RegisterUserSerializer(serializers.Serializer):
         try:
         try:
             self.full_clean_password(data)
             self.full_clean_password(data)
         except ValidationError as e:
         except ValidationError as e:
-            self._added_errors['password'] = [e]
+            self.add_error('password', e)
 
 
         validators.validate_new_registration(request, self, data)
         validators.validate_new_registration(request, self, data)
 
 

+ 117 - 24
misago/users/tests/test_auth_api.py

@@ -18,9 +18,11 @@ class GatewayTests(TestCase):
                 'password': 'nope',
                 'password': 'nope',
             }
             }
         )
         )
-
-        self.assertContains(response, "Login or password is incorrect.", status_code=400)
-
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Login or password is incorrect."],
+        })
+        
         response = self.client.get('/api/auth/')
         response = self.client.get('/api/auth/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -59,8 +61,10 @@ class GatewayTests(TestCase):
                 'password': 'Pass.123',
                 'password': 'Pass.123',
             },
             },
         )
         )
-
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Login or password is incorrect."],
+        })
 
 
         response = self.client.post(
         response = self.client.post(
             '/api/auth/',
             '/api/auth/',
@@ -109,8 +113,10 @@ class GatewayTests(TestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_login_banned(self):
     def test_login_banned(self):
         """login api fails to sign banned user in"""
         """login api fails to sign banned user in"""
@@ -235,7 +241,10 @@ class GatewayTests(TestCase):
                 'password': 'Pass.123',
                 'password': 'Pass.123',
             },
             },
         )
         )
-        self.assertContains(response, "Login or password is incorrect.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Login or password is incorrect."],
+        })
 
 
         response = self.client.get('/api/auth/')
         response = self.client.get('/api/auth/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -249,6 +258,21 @@ class UserRequirementsTests(TestCase):
         """api edge has no showstoppers"""
         """api edge has no showstoppers"""
         response = self.client.get('/api/auth/requirements/')
         response = self.client.get('/api/auth/requirements/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.json(), {
+            'username': {'max_length': 14, 'min_length': 3},
+            'password': [
+                {
+                    'name': 'UserAttributeSimilarityValidator',
+                    'user_attributes': ['username', 'email'],
+                },
+                {
+                    'name': 'MinimumLengthValidator',
+                    'min_length': 7,
+                },
+                {'name': 'CommonPasswordValidator'},
+                {'name': 'NumericPasswordValidator'},
+            ],
+        })
 
 
 
 
 class SendActivationAPITests(TestCase):
 class SendActivationAPITests(TestCase):
@@ -307,7 +331,12 @@ class SendActivationAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "No user with this e-mail exists.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        # fixme: don't leak out the info that email is invalid in auth forms
+        # instead, message that if email was valid you'll get an email
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["No user with this e-mail exists."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -315,7 +344,9 @@ class SendActivationAPITests(TestCase):
         """request activation link api errors for no body"""
         """request activation link api errors for no body"""
         response = self.client.post(self.link)
         response = self.client.post(self.link)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'email': ["This field is required."]})
+        self.assertEqual(response.json(), {
+            'email': ["This field is required."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -326,7 +357,10 @@ class SendActivationAPITests(TestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_submit_invalid_email(self):
     def test_submit_invalid_email(self):
         """request activation link api errors for invalid email"""
         """request activation link api errors for invalid email"""
@@ -336,7 +370,12 @@ class SendActivationAPITests(TestCase):
                 'email': 'fake@mail.com',
                 'email': 'fake@mail.com',
             },
             },
         )
         )
-        self.assertContains(response, "No user with this e-mail exists.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        # fixme: don't leak out the info that email is invalid in auth forms
+        # instead, message that if email was valid you'll get an email
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["No user with this e-mail exists."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -351,7 +390,10 @@ class SendActivationAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "Bob, your account is already active.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Bob, your account is already active."],
+        })
 
 
     def test_submit_inactive_user(self):
     def test_submit_inactive_user(self):
         """request activation link api errors for admin-activated users"""
         """request activation link api errors for admin-activated users"""
@@ -364,7 +406,10 @@ class SendActivationAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "only administrator may activate your account", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Bob, only administrator may activate your account."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -439,7 +484,10 @@ class SendPasswordFormAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "No user with this e-mail exists.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["No user with this e-mail exists."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -447,7 +495,9 @@ class SendPasswordFormAPITests(TestCase):
         """request change password form link api errors for no body"""
         """request change password form link api errors for no body"""
         response = self.client.post(self.link)
         response = self.client.post(self.link)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'email': ["This field is required."]})
+        self.assertEqual(response.json(), {
+            'email': ["This field is required."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -459,7 +509,10 @@ class SendPasswordFormAPITests(TestCase):
                 'email': 'fake@mail.com',
                 'email': 'fake@mail.com',
             },
             },
         )
         )
-        self.assertContains(response, "No user with this e-mail exists.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["No user with this e-mail exists."],
+        })
 
 
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
@@ -470,7 +523,10 @@ class SendPasswordFormAPITests(TestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_submit_inactive_user(self):
     def test_submit_inactive_user(self):
         """request change password form link api errors for inactive users"""
         """request change password form link api errors for inactive users"""
@@ -483,7 +539,13 @@ class SendPasswordFormAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "You have to activate your account", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': [
+                "You have to activate your account before you will "
+                "be able to request new password.",
+            ],
+        })
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
         self.user.requires_activation = 2
         self.user.requires_activation = 2
@@ -495,7 +557,13 @@ class SendPasswordFormAPITests(TestCase):
                 'email': self.user.email,
                 'email': self.user.email,
             },
             },
         )
         )
-        self.assertContains(response, "Administrator has to activate your account", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': [
+                "Administrator has to activate your account before you "
+                "will be able to request new password.",
+            ],
+        })
         self.assertTrue(not mail.outbox)
         self.assertTrue(not mail.outbox)
 
 
 
 
@@ -539,7 +607,10 @@ class ChangePasswordAPITests(TestCase):
             'false',
             'false',
             content_type="application/json",
             content_type="application/json",
         )
         )
-        self.assertContains(response, "Invalid data.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_invalid_token(self):
     def test_invalid_token(self):
         """api errors on invalid user id link"""
         """api errors on invalid user id link"""
@@ -550,7 +621,10 @@ class ChangePasswordAPITests(TestCase):
                 'token': 'invalid!',
                 'token': 'invalid!',
             },
             },
         )
         )
-        self.assertContains(response, "Form link is invalid or expired.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'token': ["Form link is invalid or expired. Please try again."],
+        })
 
 
     def test_banned_user_link(self):
     def test_banned_user_link(self):
         """request errors because user is banned"""
         """request errors because user is banned"""
@@ -588,7 +662,13 @@ class ChangePasswordAPITests(TestCase):
                 'token': make_password_change_token(self.user),
                 'token': make_password_change_token(self.user),
             },
             },
         )
         )
-        self.assertContains(response, "You have to activate your account", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': [
+                "You have to activate your account before you will "
+                "be able to change your password.",
+            ],
+        })
 
 
         self.user.requires_activation = 2
         self.user.requires_activation = 2
         self.user.save()
         self.user.save()
@@ -600,7 +680,13 @@ class ChangePasswordAPITests(TestCase):
                 'token': make_password_change_token(self.user),
                 'token': make_password_change_token(self.user),
             },
             },
         )
         )
-        self.assertContains(response, "Administrator has to activate your account", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': [
+                "Administrator has to activate your account before you "
+                "will be able to change your password.",
+            ],
+        })
 
 
     def test_disabled_user(self):
     def test_disabled_user(self):
         """change password api errors for disabled users"""
         """change password api errors for disabled users"""
@@ -609,8 +695,15 @@ class ChangePasswordAPITests(TestCase):
 
 
         response = self.client.post(self.link % self.user.pk)
         response = self.client.post(self.link % self.user.pk)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
+        self.assertEqual(response.json(), {
+            'detail': "No User matches the given query.",
+        })
 
 
     def test_submit_empty(self):
     def test_submit_empty(self):
         """change password api errors for empty body"""
         """change password api errors for empty body"""
         response = self.client.post(self.link % self.user.pk)
         response = self.client.post(self.link % self.user.pk)
-        self.assertContains(response, "This field is required.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'password': ["This field is required."],
+            'token': ["This field is required."],
+        })

+ 74 - 27
misago/users/tests/test_user_avatar_api.py

@@ -24,6 +24,16 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         super(UserAvatarTests, self).setUp()
         super(UserAvatarTests, self).setUp()
         self.link = '/api/users/%s/avatar/' % self.user.pk
         self.link = '/api/users/%s/avatar/' % self.user.pk
 
 
+    def assertAvatarChanged(self, response, detail):
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.json()['detail'], detail)
+
+        old_avatars = self.user.avatars
+
+        self.reload_user()
+        self.assertEqual(response.json()['avatars'], self.user.avatars)
+        self.assertNotEqual(response.json()['avatars'], old_avatars)
+
     def get_current_user(self):
     def get_current_user(self):
         return UserModel.objects.get(pk=self.user.pk)
         return UserModel.objects.get(pk=self.user.pk)
 
 
@@ -72,34 +82,50 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.user.save()
         self.user.save()
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
-        self.assertContains(response, "Your avatar is pwnt", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "Your avatar is locked. You can't change it.",
+            'extra': "<p>Your avatar is pwnt.</p>",
+        })
 
 
     def test_other_user_avatar(self):
     def test_other_user_avatar(self):
         """requests to api error if user tries to access other user"""
         """requests to api error if user tries to access other user"""
         self.logout_user()
         self.logout_user()
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
-        self.assertContains(response, "You have to sign in", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "You have to sign in to perform this action.",
+        })
 
 
         self.login_user(
         self.login_user(
             UserModel.objects.create_user("BobUser", "bob@bob.com", self.USER_PASSWORD)
             UserModel.objects.create_user("BobUser", "bob@bob.com", self.USER_PASSWORD)
         )
         )
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
-        self.assertContains(response, "can't change other users avatars", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "You can't change other users avatars.",
+        })
 
 
     def test_empty_requests(self):
     def test_empty_requests(self):
         """empty request errors with code 400"""
         """empty request errors with code 400"""
         response = self.client.post(self.link)
         response = self.client.post(self.link)
-        self.assertContains(response, "Unknown avatar type.", status_code=400)
-
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "Unknown avatar type.",
+        })
+        
     def test_failed_gravatar_request(self):
     def test_failed_gravatar_request(self):
         """no gravatar RPC fails"""
         """no gravatar RPC fails"""
         self.user.email_hash = 'wolololo'
         self.user.email_hash = 'wolololo'
         self.user.save()
         self.user.save()
 
 
         response = self.client.post(self.link, data={'avatar': 'gravatar'})
         response = self.client.post(self.link, data={'avatar': 'gravatar'})
-        self.assertContains(response, "No Gravatar is associated", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "No Gravatar is associated with your e-mail address.",
+        })
 
 
     def test_successful_gravatar_request(self):
     def test_successful_gravatar_request(self):
         """gravatar RPC passes"""
         """gravatar RPC passes"""
@@ -107,29 +133,33 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.user.save()
         self.user.save()
 
 
         response = self.client.post(self.link, data={'avatar': 'gravatar'})
         response = self.client.post(self.link, data={'avatar': 'gravatar'})
-        self.assertContains(response, "Gravatar was downloaded and set")
+        self.assertAvatarChanged(response, "Gravatar was downloaded and set as new avatar.")
 
 
     def test_generation_request(self):
     def test_generation_request(self):
         """generated avatar is set"""
         """generated avatar is set"""
         response = self.client.post(self.link, data={'avatar': 'generated'})
         response = self.client.post(self.link, data={'avatar': 'generated'})
-        self.assertContains(response, "New avatar based on your account")
+        self.assertAvatarChanged(response, "New avatar based on your account was set.")
 
 
     def test_avatar_upload_and_crop(self):
     def test_avatar_upload_and_crop(self):
         """avatar can be uploaded and cropped"""
         """avatar can be uploaded and cropped"""
         response = self.client.post(self.link, data={'avatar': 'generated'})
         response = self.client.post(self.link, data={'avatar': 'generated'})
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
+        self.assertAvatarChanged(response, "New avatar based on your account was set.")
 
 
         response = self.client.post(self.link, data={'avatar': 'upload'})
         response = self.client.post(self.link, data={'avatar': 'upload'})
-        self.assertContains(response, "No file was sent.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "No file was sent.",  # fixme: detail with status 400 is no no
+        })
 
 
         with open(TEST_AVATAR_PATH, 'rb') as avatar:
         with open(TEST_AVATAR_PATH, 'rb') as avatar:
             response = self.client.post(self.link, data={'avatar': 'upload', 'image': avatar})
             response = self.client.post(self.link, data={'avatar': 'upload', 'image': avatar})
             self.assertEqual(response.status_code, 200)
             self.assertEqual(response.status_code, 200)
 
 
-            response_json = response.json()
-            self.assertTrue(response_json['crop_tmp'])
+            avatar_json = response.json()
+            self.assertTrue(avatar_json['crop_tmp'])
             self.assertEqual(
             self.assertEqual(
-                self.get_current_user().avatar_tmp.url, response_json['crop_tmp']['url']
+                self.get_current_user().avatar_tmp.url, avatar_json['crop_tmp']['url']
             )
             )
 
 
         avatar = Path(self.get_current_user().avatar_tmp.path)
         avatar = Path(self.get_current_user().avatar_tmp.path)
@@ -151,9 +181,8 @@ class UserAvatarTests(AuthenticatedUserTestCase):
             content_type="application/json",
             content_type="application/json",
         )
         )
 
 
-        response_json = response.json()
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
-        self.assertContains(response, "Uploaded avatar was set.")
+        self.assertAvatarChanged(response, "Uploaded avatar was set.")
 
 
         self.assertFalse(self.get_current_user().avatar_tmp)
         self.assertFalse(self.get_current_user().avatar_tmp)
 
 
@@ -175,7 +204,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
             }),
             }),
             content_type="application/json",
             content_type="application/json",
         )
         )
-        self.assertContains(response, "This avatar type is not allowed.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "This avatar type is not allowed.",
+        })
 
 
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
@@ -191,7 +223,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
             }),
             }),
             content_type="application/json",
             content_type="application/json",
         )
         )
-        self.assertContains(response, "Avatar was re-cropped.")
+        self.assertAvatarChanged(response, "Avatar was re-cropped.")
 
 
         # delete user avatars, test if it deletes src and tmp
         # delete user avatars, test if it deletes src and tmp
         store.delete_avatar(self.get_current_user())
         store.delete_avatar(self.get_current_user())
@@ -208,8 +240,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response = self.client.post(self.link, data={'avatar': 'galleries', 'image': 123})
         response = self.client.post(self.link, data={'avatar': 'galleries', 'image': 123})
-
-        self.assertContains(response, "This avatar type is not allowed.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "This avatar type is not allowed.",
+        })
 
 
     def test_gallery_image_validation(self):
     def test_gallery_image_validation(self):
         """gallery validates image to set"""
         """gallery validates image to set"""
@@ -225,7 +259,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                 'avatar': 'galleries',
                 'avatar': 'galleries',
             },
             },
         )
         )
-        self.assertContains(response, "Incorrect image.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "Incorrect image.",
+        })
 
 
         # invalid id is handled
         # invalid id is handled
         response = self.client.post(
         response = self.client.post(
@@ -235,7 +272,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                 'image': 'asdsadsadsa',
                 'image': 'asdsadsadsa',
             },
             },
         )
         )
-        self.assertContains(response, "Incorrect image.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "Incorrect image.",
+        })
 
 
         # nonexistant image is handled
         # nonexistant image is handled
         response = self.client.get(self.link)
         response = self.client.get(self.link)
@@ -252,13 +292,19 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                 'image': test_avatar + 5000,
                 'image': test_avatar + 5000,
             },
             },
         )
         )
-        self.assertContains(response, "Incorrect image.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "Incorrect image.",
+        })
 
 
         # default gallery image is handled
         # default gallery image is handled
         AvatarGallery.objects.filter(pk=test_avatar).update(gallery=gallery.DEFAULT_GALLERY)
         AvatarGallery.objects.filter(pk=test_avatar).update(gallery=gallery.DEFAULT_GALLERY)
 
 
         response = self.client.post(self.link, data={'avatar': 'galleries', 'image': test_avatar})
         response = self.client.post(self.link, data={'avatar': 'galleries', 'image': test_avatar})
-        self.assertContains(response, "Incorrect image.", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'detail': "Incorrect image.",
+        })
 
 
     def test_gallery_set_valid_avatar(self):
     def test_gallery_set_valid_avatar(self):
         """its possible to set avatar from gallery"""
         """its possible to set avatar from gallery"""
@@ -278,8 +324,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                 'image': test_avatar,
                 'image': test_avatar,
             },
             },
         )
         )
-
-        self.assertContains(response, "Avatar from gallery was set.")
+        self.assertAvatarChanged(response, "Avatar from gallery was set.")
 
 
 
 
 class UserAvatarModerationTests(AuthenticatedUserTestCase):
 class UserAvatarModerationTests(AuthenticatedUserTestCase):
@@ -299,7 +344,10 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         })
         })
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
-        self.assertContains(response, "can't moderate avatars", status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "You can't moderate avatars.",
+        })
 
 
     def test_moderate_avatar(self):
     def test_moderate_avatar(self):
         """moderate avatar"""
         """moderate avatar"""
@@ -335,12 +383,11 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         other_user = UserModel.objects.get(pk=self.other_user.pk)
         other_user = UserModel.objects.get(pk=self.other_user.pk)
-
-        options = response.json()
         self.assertEqual(other_user.is_avatar_locked, True)
         self.assertEqual(other_user.is_avatar_locked, True)
         self.assertEqual(other_user.avatar_lock_user_message, "Test user message.")
         self.assertEqual(other_user.avatar_lock_user_message, "Test user message.")
         self.assertEqual(other_user.avatar_lock_staff_message, "Test staff message.")
         self.assertEqual(other_user.avatar_lock_staff_message, "Test staff message.")
 
 
+        options = response.json()
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)

+ 36 - 10
misago/users/tests/test_user_create_api.py

@@ -22,6 +22,11 @@ class UserCreateTests(UserTestCase):
         """empty request errors with code 400"""
         """empty request errors with code 400"""
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'email': ["This field is required."],
+            'password': ["This field is required."],
+            'username': ["This field is required."],
+        })
 
 
     def test_invalid_data(self):
     def test_invalid_data(self):
         """invalid request data errors with code 400"""
         """invalid request data errors with code 400"""
@@ -31,19 +36,28 @@ class UserCreateTests(UserTestCase):
             content_type="application/json",
             content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'non_field_errors': ["Invalid data. Expected a dictionary, but got bool."],
+        })
 
 
     def test_authenticated_request(self):
     def test_authenticated_request(self):
         """authentiated user request errors with code 403"""
         """authentiated user request errors with code 403"""
         self.login_user(self.get_authenticated_user())
         self.login_user(self.get_authenticated_user())
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "This action is not available to signed in users.",
+        })
 
 
     def test_registration_off_request(self):
     def test_registration_off_request(self):
         """registrations off request errors with code 403"""
         """registrations off request errors with code 403"""
         settings.override_setting('account_activation', 'closed')
         settings.override_setting('account_activation', 'closed')
 
 
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
-        self.assertContains(response, 'closed', status_code=403)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': "New users registrations are currently closed.",
+        })
 
 
     def test_registration_validates_ip_ban(self):
     def test_registration_validates_ip_ban(self):
         """api validates ip ban"""
         """api validates ip ban"""
@@ -61,8 +75,14 @@ class UserCreateTests(UserTestCase):
                 'password': 'LoremP4ssword',
                 'password': 'LoremP4ssword',
             },
             },
         )
         )
-
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.json(), {
+            'detail': {
+                'html': '<p>You can&#39;t register account like this.</p>',
+                'plain': "You can't register account like this."
+            },
+            'expires_on': None,
+        })
 
 
     def test_registration_validates_ip_registration_ban(self):
     def test_registration_validates_ip_registration_ban(self):
         """api validates ip registration-only ban"""
         """api validates ip registration-only ban"""
@@ -150,7 +170,6 @@ class UserCreateTests(UserTestCase):
                 'password': 'LoremP4ssword',
                 'password': 'LoremP4ssword',
             },
             },
         )
         )
-
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(), {
             response.json(), {
@@ -192,7 +211,7 @@ class UserCreateTests(UserTestCase):
                 'password': 'LoremP4ssword',
                 'password': 'LoremP4ssword',
             },
             },
         )
         )
-
+        
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.json(), {
         self.assertEqual(response.json(), {
             'email': ["You can't register account like this."],
             'email': ["You can't register account like this."],
@@ -231,10 +250,14 @@ class UserCreateTests(UserTestCase):
                 'password': '123',
                 'password': '123',
             },
             },
         )
         )
-
-        self.assertContains(response, "password is too short", status_code=400)
-        self.assertContains(response, "password is entirely numeric", status_code=400)
-        self.assertContains(response, "email is not allowed", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'password': [
+                "This password is too short. It must contain at least 7 characters.",
+                "This password is entirely numeric.",
+            ],
+            'email': ["This email is not allowed."],
+        })
 
 
     def test_registration_validates_password_similiarity(self):
     def test_registration_validates_password_similiarity(self):
         """api uses validate_password to validate registrations"""
         """api uses validate_password to validate registrations"""
@@ -246,8 +269,11 @@ class UserCreateTests(UserTestCase):
                 'password': 'BobBoberson',
                 'password': 'BobBoberson',
             },
             },
         )
         )
-
-        self.assertContains(response, "password is too similar to the username", status_code=400)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(response.json(), {
+            'password': ["The password is too similar to the username."],
+            'email': ["This email is not allowed."],
+        })
 
 
     @override_settings(captcha_type='qa', qa_question='Test', qa_answers='Lorem\nIpsum')
     @override_settings(captcha_type='qa', qa_question='Test', qa_answers='Lorem\nIpsum')
     def test_registration_validates_captcha(self):
     def test_registration_validates_captcha(self):