Commit 9fcc1c32 by wajeeha-khalid

Merge pull request #10586 from edx/jia/MA-862

MA-862; accept application/merge-patch+json for comment/thread update
parents 08087861 4ea70aeb
"""
Utility Mixins for unit tests
"""
import json
import sys import sys
from mock import patch from mock import patch
...@@ -90,3 +95,16 @@ class EventTestMixin(object): ...@@ -90,3 +95,16 @@ class EventTestMixin(object):
Reset the mock tracker in order to forget about old events. Reset the mock tracker in order to forget about old events.
""" """
self.mock_tracker.reset_mock() self.mock_tracker.reset_mock()
class PatchMediaTypeMixin(object):
"""
Generic mixin for verifying unsupported media type in PATCH
"""
def test_patch_unsupported_media_type(self):
response = self.client.patch( # pylint: disable=no-member
self.url,
json.dumps({}),
content_type=self.unsupported_media_type
)
self.assertEqual(response.status_code, 415)
...@@ -11,6 +11,7 @@ import mock ...@@ -11,6 +11,7 @@ import mock
from pytz import UTC from pytz import UTC
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from rest_framework.parsers import JSONParser
from rest_framework.test import APIClient from rest_framework.test import APIClient
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
...@@ -24,7 +25,7 @@ from discussion_api.tests.utils import ( ...@@ -24,7 +25,7 @@ from discussion_api.tests.utils import (
make_minimal_cs_thread, make_minimal_cs_thread,
) )
from student.tests.factories import CourseEnrollmentFactory, UserFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import UrlResetMixin from util.testing import UrlResetMixin, PatchMediaTypeMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls, ItemFactory
...@@ -536,9 +537,10 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -536,9 +537,10 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@httpretty.activate @httpretty.activate
@disable_signal(api, 'thread_edited') @disable_signal(api, 'thread_edited')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, PatchMediaTypeMixin):
"""Tests for ThreadViewSet partial_update""" """Tests for ThreadViewSet partial_update"""
def setUp(self): def setUp(self):
self.unsupported_media_type = JSONParser.media_type
super(ThreadViewSetPartialUpdateTest, self).setUp() super(ThreadViewSetPartialUpdateTest, self).setUp()
self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"}) self.url = reverse("thread-detail", kwargs={"thread_id": "test_thread"})
...@@ -592,7 +594,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest ...@@ -592,7 +594,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
response = self.client.patch( # pylint: disable=no-member response = self.client.patch( # pylint: disable=no-member
self.url, self.url,
json.dumps(request_data), json.dumps(request_data),
content_type="application/json" content_type="application/merge-patch+json"
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content) response_data = json.loads(response.content)
...@@ -625,7 +627,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest ...@@ -625,7 +627,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
response = self.client.patch( # pylint: disable=no-member response = self.client.patch( # pylint: disable=no-member
self.url, self.url,
json.dumps(request_data), json.dumps(request_data),
content_type="application/json" content_type="application/merge-patch+json"
) )
expected_response_data = { expected_response_data = {
"field_errors": {"title": {"developer_message": "This field may not be blank."}} "field_errors": {"title": {"developer_message": "This field may not be blank."}}
...@@ -930,9 +932,10 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): ...@@ -930,9 +932,10 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@disable_signal(api, 'comment_edited') @disable_signal(api, 'comment_edited')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase): class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, PatchMediaTypeMixin):
"""Tests for CommentViewSet partial_update""" """Tests for CommentViewSet partial_update"""
def setUp(self): def setUp(self):
self.unsupported_media_type = JSONParser.media_type
super(CommentViewSetPartialUpdateTest, self).setUp() super(CommentViewSetPartialUpdateTest, self).setUp()
httpretty.reset() httpretty.reset()
httpretty.enable() httpretty.enable()
...@@ -982,7 +985,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes ...@@ -982,7 +985,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
response = self.client.patch( # pylint: disable=no-member response = self.client.patch( # pylint: disable=no-member
self.url, self.url,
json.dumps(request_data), json.dumps(request_data),
content_type="application/json" content_type="application/merge-patch+json"
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content) response_data = json.loads(response.content)
...@@ -1004,7 +1007,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes ...@@ -1004,7 +1007,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
response = self.client.patch( # pylint: disable=no-member response = self.client.patch( # pylint: disable=no-member
self.url, self.url,
json.dumps(request_data), json.dumps(request_data),
content_type="application/json" content_type="application/merge-patch+json"
) )
expected_response_data = { expected_response_data = {
"field_errors": {"raw_body": {"developer_message": "This field may not be blank."}} "field_errors": {"raw_body": {"developer_message": "This field may not be blank."}}
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Discussion API views Discussion API views
""" """
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from rest_framework.exceptions import UnsupportedMediaType
from rest_framework.parsers import JSONParser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
...@@ -25,6 +27,7 @@ from discussion_api.api import ( ...@@ -25,6 +27,7 @@ from discussion_api.api import (
update_thread, update_thread,
) )
from discussion_api.forms import CommentListGetForm, ThreadListGetForm, _PaginationForm from discussion_api.forms import CommentListGetForm, ThreadListGetForm, _PaginationForm
from openedx.core.lib.api.parsers import MergePatchParser
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
...@@ -119,6 +122,7 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -119,6 +122,7 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
PATCH /api/discussion/v1/threads/thread_id PATCH /api/discussion/v1/threads/thread_id
{"raw_body": "Edited text"} {"raw_body": "Edited text"}
Content Type: "application/merge-patch+json"
DELETE /api/discussion/v1/threads/thread_id DELETE /api/discussion/v1/threads/thread_id
...@@ -175,6 +179,9 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -175,6 +179,9 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
topic_id, type, title, and raw_body are accepted with the same meaning topic_id, type, title, and raw_body are accepted with the same meaning
as in a POST request as in a POST request
If "application/merge-patch+json" is not the specified content type,
a 415 error is returned.
**GET Response Values**: **GET Response Values**:
* results: The list of threads; each item in the list has the same * results: The list of threads; each item in the list has the same
...@@ -232,6 +239,7 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -232,6 +239,7 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
""" """
lookup_field = "thread_id" lookup_field = "thread_id"
parser_classes = (JSONParser, MergePatchParser,)
def list(self, request): def list(self, request):
""" """
...@@ -274,6 +282,8 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -274,6 +282,8 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
Implements the PATCH method for the instance endpoint as described in Implements the PATCH method for the instance endpoint as described in
the class docstring. the class docstring.
""" """
if request.content_type != MergePatchParser.media_type:
raise UnsupportedMediaType(request.content_type)
return Response(update_thread(request, thread_id, request.data)) return Response(update_thread(request, thread_id, request.data))
def destroy(self, request, thread_id): def destroy(self, request, thread_id):
...@@ -307,6 +317,7 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -307,6 +317,7 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
PATCH /api/discussion/v1/comments/comment_id PATCH /api/discussion/v1/comments/comment_id
{"raw_body": "Edited text"} {"raw_body": "Edited text"}
Content Type: "application/merge-patch+json"
DELETE /api/discussion/v1/comments/comment_id DELETE /api/discussion/v1/comments/comment_id
...@@ -347,6 +358,9 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -347,6 +358,9 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
raw_body is accepted with the same meaning as in a POST request raw_body is accepted with the same meaning as in a POST request
If "application/merge-patch+json" is not the specified content type,
a 415 error is returned.
**GET Response Values**: **GET Response Values**:
* results: The list of comments; each item in the list has the same * results: The list of comments; each item in the list has the same
...@@ -409,6 +423,7 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -409,6 +423,7 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
""" """
lookup_field = "comment_id" lookup_field = "comment_id"
parser_classes = (JSONParser, MergePatchParser,)
def list(self, request): def list(self, request):
""" """
...@@ -465,4 +480,6 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet): ...@@ -465,4 +480,6 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
Implements the PATCH method for the instance endpoint as described in Implements the PATCH method for the instance endpoint as described in
the class docstring. the class docstring.
""" """
if request.content_type != MergePatchParser.media_type:
raise UnsupportedMediaType(request.content_type)
return Response(update_comment(request, comment_id, request.data)) return Response(update_comment(request, comment_id, request.data))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment