|
@@ -0,0 +1,241 @@
|
|
|
+from django.core.exceptions import PermissionDenied
|
|
|
+from django.http import Http404
|
|
|
+from django.test import TestCase
|
|
|
+
|
|
|
+from misago.core.apipatch import ApiPatch, InvalidAction
|
|
|
+
|
|
|
+
|
|
|
+class MockRequest(object):
|
|
|
+ def __init__(self, data=None):
|
|
|
+ self.data = data
|
|
|
+
|
|
|
+
|
|
|
+class MockObject(object):
|
|
|
+ def __init__(self, pk):
|
|
|
+ self.id = pk
|
|
|
+ self.pk = pk
|
|
|
+
|
|
|
+
|
|
|
+class ApiPatchTests(TestCase):
|
|
|
+ def test_add(self):
|
|
|
+ """add method adds function to patch object"""
|
|
|
+ patch = ApiPatch()
|
|
|
+
|
|
|
+ def test_function():
|
|
|
+ pass
|
|
|
+ patch.add('test-add', test_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'], test_function)
|
|
|
+
|
|
|
+ def test_remove(self):
|
|
|
+ """remove method adds function to patch object"""
|
|
|
+ patch = ApiPatch()
|
|
|
+
|
|
|
+ def test_function():
|
|
|
+ pass
|
|
|
+ patch.remove('test-remove', test_function)
|
|
|
+
|
|
|
+ self.assertEqual(len(patch._actions), 1)
|
|
|
+ self.assertEqual(patch._actions[0]['op'], 'remove')
|
|
|
+ self.assertEqual(patch._actions[0]['path'], 'test-remove')
|
|
|
+ self.assertEqual(patch._actions[0]['handler'], test_function)
|
|
|
+
|
|
|
+ def test_replace(self):
|
|
|
+ """replace method adds function to patch object"""
|
|
|
+ patch = ApiPatch()
|
|
|
+
|
|
|
+ def test_function():
|
|
|
+ pass
|
|
|
+ patch.replace('test-replace', test_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'], test_function)
|
|
|
+
|
|
|
+ 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], u"undefined op")
|
|
|
+
|
|
|
+ # 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_dispatch(self):
|
|
|
+ """dispatch calls actions and returns response"""
|
|
|
+ patch = ApiPatch()
|
|
|
+
|
|
|
+ def action_error(request, target, value):
|
|
|
+ if value == '404':
|
|
|
+ raise Http404()
|
|
|
+ if value == 'perm':
|
|
|
+ raise PermissionDenied("yo ain't doing that!")
|
|
|
+ return {}
|
|
|
+ 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 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(len(response.data['detail']), 3)
|
|
|
+ self.assertEqual(response.data['detail'][0], 'ok')
|
|
|
+ self.assertEqual(response.data['detail'][1], 'ok')
|
|
|
+ self.assertEqual(response.data['detail'][2], 'ok')
|
|
|
+ self.assertEqual(response.data['id'], 13)
|
|
|
+ self.assertEqual(response.data['value'], 14)
|
|
|
+
|
|
|
+ # 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(len(response.data['detail']), 3)
|
|
|
+ self.assertEqual(response.data['detail'][0], 'ok')
|
|
|
+ self.assertEqual(response.data['detail'][1], 'ok')
|
|
|
+ self.assertEqual(
|
|
|
+ response.data['detail'][2], '"replace" op has to specify path')
|
|
|
+ self.assertEqual(response.data['id'], 13)
|
|
|
+ self.assertEqual(response.data['value'], 12)
|
|
|
+
|
|
|
+ # 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, 400)
|
|
|
+
|
|
|
+ self.assertEqual(len(response.data['detail']), 2)
|
|
|
+ self.assertEqual(response.data['detail'][0], 'ok')
|
|
|
+ self.assertEqual(
|
|
|
+ response.data['detail'][1], "NOT FOUND")
|
|
|
+ self.assertEqual(response.data['id'], 13)
|
|
|
+ self.assertEqual(response.data['value'], 4)
|
|
|
+
|
|
|
+ # 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, 400)
|
|
|
+
|
|
|
+ self.assertEqual(len(response.data['detail']), 4)
|
|
|
+ self.assertEqual(response.data['detail'][0], 'ok')
|
|
|
+ self.assertEqual(response.data['detail'][1], 'ok')
|
|
|
+ self.assertEqual(response.data['detail'][2], 'ok')
|
|
|
+ self.assertEqual(
|
|
|
+ response.data['detail'][3], "yo ain't doing that!")
|
|
|
+ self.assertEqual(response.data['id'], 13)
|
|
|
+ self.assertEqual(response.data['value'], 18)
|