def test_replace(self): """replace method adds function to patch object""" patch = ApiPatch() def mock_function(): pass patch.replace('test-replace', mock_function) self.assertEqual(len(patch._actions), 1) self.assertEqual(patch._actions[0]['op'], 'replace') self.assertEqual(patch._actions[0]['path'], 'test-replace') self.assertEqual(patch._actions[0]['handler'], mock_function)
def test_add(self): """add method adds function to patch object""" patch = ApiPatch() def mock_function(): pass patch.add('test-add', mock_function) self.assertEqual(len(patch._actions), 1) self.assertEqual(patch._actions[0]['op'], 'add') self.assertEqual(patch._actions[0]['path'], 'test-add') self.assertEqual(patch._actions[0]['handler'], mock_function)
from misago.threads.participants import (add_participant, change_owner, make_participants_aware, remove_participant) from misago.threads.permissions import ( allow_add_participant, allow_add_participants, allow_approve_thread, allow_change_owner, allow_edit_thread, allow_pin_thread, allow_hide_thread, allow_move_thread, allow_remove_participant, allow_start_thread, allow_unhide_thread) from misago.threads.serializers import ThreadParticipantSerializer from misago.threads.validators import validate_title PATCH_LIMIT = settings.MISAGO_THREADS_PER_PAGE + settings.MISAGO_THREADS_TAIL UserModel = get_user_model() thread_patch_dispatcher = ApiPatch() def patch_acl(request, thread, value): """useful little op that updates thread acl to current state""" if value: add_acl(request.user, thread) return {'acl': thread.acl} else: return {'acl': None} thread_patch_dispatcher.add('acl', patch_acl) def patch_title(request, thread, value):
from django.utils.translation import ugettext as _ from misago.acl import add_acl from misago.api.patch import ApiPatch from misago.conf import settings from misago.threads.models import PostLike from misago.threads import moderation from misago.threads.permissions import ( allow_approve_post, allow_hide_best_answer, allow_hide_post, allow_protect_post, allow_unhide_post) from misago.threads.permissions import exclude_invisible_posts PATCH_LIMIT = settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL post_patch_dispatcher = ApiPatch() def patch_acl(request, post, value): """useful little op that updates post acl to current state""" if value: add_acl(request.user, post) return {'acl': post.acl} else: return {'acl': None} post_patch_dispatcher.add('acl', patch_acl) def patch_is_liked(request, post, value):
def test_dispatch_bulk(self): """dispatch_bulk calls actions and returns response""" patch = ApiPatch() def action_error(request, target, value): if value == '404': raise Http404() if value == '404_reason': raise Http404("something was removed") if value == 'perm': raise PermissionDenied("yo ain't doing that!") if value == 'invalid': raise ValidationError("invalid data here!") if value == 'api_invalid': raise ApiValidationError("invalid api data here!") patch.replace('error', action_error) def action_mutate(request, target, value): return {'value': value * 2} patch.replace('mutate', action_mutate) # valid bulk dispatch response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '200', 'patch': { 'value': 14 } }, { 'id': '7', 'status': '200', 'patch': { 'value': 14 } }, ]) # invalid action in bulk dispatch response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 400) self.assertEqual(response.data, { 'detail': '"replace" op has to specify path.', }) # op raised validation error response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'invalid', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '400', 'detail': ["invalid data here!"] }, { 'id': '7', 'status': '400', 'detail': ["invalid data here!"] }, ]) # op raised api validation error response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'api_invalid', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '400', 'detail': ["invalid api data here!"] }, { 'id': '7', 'status': '400', 'detail': ["invalid api data here!"] }, ]) # action in bulk dispatch raised perm denied response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 9, }, { 'op': 'replace', 'path': 'error', 'value': 'perm', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '403', 'detail': "yo ain't doing that!" }, { 'id': '7', 'status': '403', 'detail': "yo ain't doing that!" }, ]) # action in bulk dispatch raised 404 response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'error', 'value': '404', }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '404', 'detail': "NOT FOUND" }, { 'id': '7', 'status': '404', 'detail': "NOT FOUND" }, ]) # action in bulk dispatch raised 404 with message response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'error', 'value': '404_reason', }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '404', 'detail': "something was removed" }, { 'id': '7', 'status': '404', 'detail': "something was removed" }, ])
def test_dispatch(self): """dispatch calls actions and returns response""" patch = ApiPatch() def action_error(request, target, value): if value == '404': raise Http404() if value == '404_reason': raise Http404("something was removed") if value == 'perm': raise PermissionDenied("yo ain't doing that!") if value == 'invalid': raise ValidationError("invalid data here!") if value == 'api_invalid': raise ApiValidationError("invalid api data here!") patch.replace('error', action_error) def action_mutate(request, target, value): return {'value': value * 2} patch.replace('mutate', action_mutate) # dispatch requires list as an argument response = patch.dispatch(MockRequest({}), {}) self.assertEqual(response.status_code, 400) self.assertEqual(response.data['detail'], "PATCH request should be a list of operations.") # valid dispatch response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, {'value': 14, 'id': 13}) # invalid action in dispatch response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 400) self.assertEqual(response.data['detail'], '"replace" op has to specify path.') # op raised validation error response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'invalid', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 400) self.assertEqual(response.data['detail'], ["invalid data here!"]) # op raised api validation error response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'api_invalid', }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 400) self.assertEqual(response.data['detail'], ["invalid api data here!"]) # action in dispatch raised perm denied response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 9, }, { 'op': 'replace', 'path': 'error', 'value': 'perm', }, ]), MockObject(13)) self.assertEqual(response.status_code, 403) self.assertEqual(response.data['detail'], "yo ain't doing that!") # action in dispatch raised 404 response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'error', 'value': '404', }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 404) self.assertEqual(response.data['detail'], "NOT FOUND") # action in dispatch raised 404 with message response = patch.dispatch( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'error', 'value': '404_reason', }, { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 7, }, ]), MockObject(13)) self.assertEqual(response.status_code, 404) self.assertEqual(response.data['detail'], "something was removed")
from django.core.exceptions import PermissionDenied from django.utils.translation import ugettext as _ from misago.acl import add_acl from misago.api.patch import ApiPatch from misago.threads.moderation import posts as moderation from misago.threads.permissions import allow_hide_event, allow_unhide_event event_patch_dispatcher = ApiPatch() def patch_acl(request, event, value): """useful little op that updates event acl to current state""" if value: add_acl(request.user, event) return {'acl': event.acl} else: return {'acl': None} event_patch_dispatcher.add('acl', patch_acl) def patch_is_hidden(request, event, value): if value: allow_hide_event(request.user, event) moderation.hide_post(request.user, event) else: allow_unhide_event(request.user, event) moderation.unhide_post(request.user, event)
def test_validate_action(self): """validate_action method validates action dict""" patch = ApiPatch() VALID_ACTIONS = [ { 'op': 'add', 'path': 'test', 'value': 42 }, { 'op': 'remove', 'path': 'other-test', 'value': 'Lorem' }, { 'op': 'replace', 'path': 'false-test', 'value': None }, ] for action in VALID_ACTIONS: patch.validate_action(action) # undefined op UNSUPPORTED_ACTIONS = ({}, {'op': ''}, {'no': 'op'}, ) for action in UNSUPPORTED_ACTIONS: try: patch.validate_action(action) except InvalidAction as e: self.assertEqual(e.args[0], '"op" parameter must be defined.') # unsupported op try: patch.validate_action({'op': 'nope'}) except InvalidAction as e: self.assertEqual(e.args[0], u'"nope" op is unsupported.') # op lacking patch try: patch.validate_action({'op': 'add'}) except InvalidAction as e: self.assertEqual(e.args[0], u'"add" op has to specify path.') # op lacking value try: patch.validate_action({ 'op': 'add', 'path': 'yolo', }) except InvalidAction as e: self.assertEqual(e.args[0], u'"add" op has to specify value.') # empty value is allowed try: patch.validate_action({ 'op': 'add', 'path': 'yolo', 'value': '', }) except InvalidAction as e: self.assertEqual(e.args[0], u'"add" op has to specify value.')
def test_dispatch_action(self): """dispatch_action calls specified actions""" patch = ApiPatch() mock_target = MockObject(13) def action_a(request, target, value): self.assertEqual(request, 'request') self.assertEqual(target, mock_target) return {'a': value * 2, 'b': 111} patch.replace('abc', action_a) def action_b(request, target, value): self.assertEqual(request, 'request') self.assertEqual(target, mock_target) return {'b': value * 10} patch.replace('abc', action_b) def action_fail(request, target, value): self.fail("unrequired action was called") patch.add('c', action_fail) patch.remove('c', action_fail) patch.replace('c', action_fail) patch_dict = {'id': 123} patch.dispatch_action( patch_dict, 'request', mock_target, { 'op': 'replace', 'path': 'abc', 'value': 5, } ) self.assertEqual(len(patch_dict), 3) self.assertEqual(patch_dict['id'], 123) self.assertEqual(patch_dict['a'], 10) self.assertEqual(patch_dict['b'], 50)
def test_validate_actions(self): """validate_actions method validates action dict""" patch = ApiPatch() VALID_ACTIONS = [ { 'op': 'add', 'path': 'test', 'value': 42 }, { 'op': 'remove', 'path': 'other-test', 'value': 'Lorem' }, { 'op': 'replace', 'path': 'false-test', 'value': None }, ] for action in VALID_ACTIONS: patch.validate_actions([action]) # undefined op UNSUPPORTED_ACTIONS = ( {}, { 'op': '' }, { 'no': 'op' }, ) for action in UNSUPPORTED_ACTIONS: try: patch.validate_actions([action]) except InvalidAction as e: self.assertEqual(e.args[0], '"op" parameter must be defined.') # unsupported op try: patch.validate_actions([{'op': 'nope'}]) except InvalidAction as e: self.assertEqual(e.args[0], '"nope" op is unsupported.') # op lacking patch try: patch.validate_actions([{'op': 'add'}]) except InvalidAction as e: self.assertEqual(e.args[0], '"add" op has to specify path.') # op lacking value try: patch.validate_actions([{ 'op': 'add', 'path': 'yolo', }]) except InvalidAction as e: self.assertEqual(e.args[0], '"add" op has to specify value.') # empty value is forbidden try: patch.validate_actions([{ 'op': 'add', 'path': 'yolo', 'value': '', }]) except InvalidAction as e: self.assertEqual(e.args[0], '"add" op has to specify value.') # duplicated actions are forbidden try: patch.validate_actions([ { 'op': 'add', 'path': 'like', 'value': True }, { 'op': 'add', 'path': 'like', 'value': False }, ]) except InvalidAction as e: self.assertEqual(e.args[0], '"add" op for "like" path is repeated.')
def test_dispatch_bulk(self): """dispatch_bulk calls actions and returns response""" patch = ApiPatch() def action_error(request, target, value): if value == '404': raise Http404() if value == '404_reason': raise Http404("something was removed") if value == 'perm': raise PermissionDenied("yo ain't doing that!") if value == 'invalid': raise ValidationError("invalid data here!") if value == 'api_invalid': raise ApiValidationError("invalid api data here!") patch.replace('error', action_error) def action_mutate(request, target, value): return {'value': value * 2} patch.replace('mutate', action_mutate) # valid bulk dispatch response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '200', 'patch': { 'value': 12 } }, { 'id': '7', 'status': '200', 'patch': { 'value': 12 } }, ]) # dispatch requires list as an argument response = patch.dispatch_bulk(MockRequest( {}), [MockObject(5), MockObject(7)]) self.assertEqual(response.status_code, 400) self.assertEqual( response.data, { 'non_field_errors': ["PATCH request should be a list of operations."], }) # invalid action in bulk dispatch response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 400) self.assertEqual(response.data, { 'non_field_errors': ['"replace" op has to specify path.'], }) # repeated action in dispatch response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'mutate', 'value': 12, }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 400) self.assertEqual(response.data, { 'non_field_errors': ['"replace" op for "mutate" path is repeated.'], }) # op raised validation error response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'invalid', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '400', 'value': ["invalid data here!"] }, { 'id': '7', 'status': '400', 'value': ["invalid data here!"] }, ]) # op raised api validation error response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'api_invalid', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '400', 'value': ["invalid api data here!"] }, { 'id': '7', 'status': '400', 'value': ["invalid api data here!"] }, ]) # action in bulk dispatch raised perm denied response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': 'perm', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '403', 'detail': "yo ain't doing that!" }, { 'id': '7', 'status': '403', 'detail': "yo ain't doing that!" }, ]) # action in bulk dispatch raised 404 response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 6, }, { 'op': 'replace', 'path': 'error', 'value': '404', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '404', 'detail': 'NOT FOUND' }, { 'id': '7', 'status': '404', 'detail': 'NOT FOUND' }, ]) # action in dispatch raised 404 with message but didn't expose it response = patch.dispatch_bulk( MockRequest([ { 'op': 'replace', 'path': 'mutate', 'value': 2, }, { 'op': 'replace', 'path': 'error', 'value': '404_reason', }, ]), [MockObject(5), MockObject(7)], ) self.assertEqual(response.status_code, 200) self.assertEqual(response.data, [ { 'id': '5', 'status': '404', 'detail': 'NOT FOUND' }, { 'id': '7', 'status': '404', 'detail': 'NOT FOUND' }, ])