test_apipatch.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. from django.core.exceptions import PermissionDenied
  2. from django.http import Http404
  3. from django.test import TestCase
  4. from misago.core.apipatch import ApiPatch, InvalidAction
  5. class MockRequest(object):
  6. def __init__(self, data=None):
  7. self.data = data
  8. class MockObject(object):
  9. def __init__(self, pk):
  10. self.id = pk
  11. self.pk = pk
  12. class ApiPatchTests(TestCase):
  13. def test_add(self):
  14. """add method adds function to patch object"""
  15. patch = ApiPatch()
  16. def mock_function():
  17. pass
  18. patch.add('test-add', mock_function)
  19. self.assertEqual(len(patch._actions), 1)
  20. self.assertEqual(patch._actions[0]['op'], 'add')
  21. self.assertEqual(patch._actions[0]['path'], 'test-add')
  22. self.assertEqual(patch._actions[0]['handler'], mock_function)
  23. def test_remove(self):
  24. """remove method adds function to patch object"""
  25. patch = ApiPatch()
  26. def mock_function():
  27. pass
  28. patch.remove('test-remove', mock_function)
  29. self.assertEqual(len(patch._actions), 1)
  30. self.assertEqual(patch._actions[0]['op'], 'remove')
  31. self.assertEqual(patch._actions[0]['path'], 'test-remove')
  32. self.assertEqual(patch._actions[0]['handler'], mock_function)
  33. def test_replace(self):
  34. """replace method adds function to patch object"""
  35. patch = ApiPatch()
  36. def mock_function():
  37. pass
  38. patch.replace('test-replace', mock_function)
  39. self.assertEqual(len(patch._actions), 1)
  40. self.assertEqual(patch._actions[0]['op'], 'replace')
  41. self.assertEqual(patch._actions[0]['path'], 'test-replace')
  42. self.assertEqual(patch._actions[0]['handler'], mock_function)
  43. def test_validate_action(self):
  44. """validate_action method validates action dict"""
  45. patch = ApiPatch()
  46. VALID_ACTIONS = [
  47. {
  48. 'op': 'add',
  49. 'path': 'test',
  50. 'value': 42
  51. },
  52. {
  53. 'op': 'remove',
  54. 'path': 'other-test',
  55. 'value': 'Lorem'
  56. },
  57. {
  58. 'op': 'replace',
  59. 'path': 'false-test',
  60. 'value': None
  61. },
  62. ]
  63. for action in VALID_ACTIONS:
  64. patch.validate_action(action)
  65. # undefined op
  66. UNSUPPORTED_ACTIONS = ({}, {'op': ''}, {'no': 'op'}, )
  67. for action in UNSUPPORTED_ACTIONS:
  68. try:
  69. patch.validate_action(action)
  70. except InvalidAction as e:
  71. self.assertEqual(e.args[0], "undefined op")
  72. # unsupported op
  73. try:
  74. patch.validate_action({'op': 'nope'})
  75. except InvalidAction as e:
  76. self.assertEqual(e.args[0], '"nope" op is unsupported')
  77. # op lacking patch
  78. try:
  79. patch.validate_action({'op': 'add'})
  80. except InvalidAction as e:
  81. self.assertEqual(e.args[0], '"add" op has to specify path')
  82. # op lacking value
  83. try:
  84. patch.validate_action({
  85. 'op': 'add',
  86. 'path': 'yolo',
  87. })
  88. except InvalidAction as e:
  89. self.assertEqual(e.args[0], '"add" op has to specify value')
  90. # empty value is allowed
  91. try:
  92. patch.validate_action({
  93. 'op': 'add',
  94. 'path': 'yolo',
  95. 'value': '',
  96. })
  97. except InvalidAction as e:
  98. self.assertEqual(e.args[0], '"add" op has to specify value')
  99. def test_dispatch_action(self):
  100. """dispatch_action calls specified actions"""
  101. patch = ApiPatch()
  102. mock_target = MockObject(13)
  103. def action_a(request, target, value):
  104. self.assertEqual(request, 'request')
  105. self.assertEqual(target, mock_target)
  106. return {'a': value * 2, 'b': 111}
  107. patch.replace('abc', action_a)
  108. def action_b(request, target, value):
  109. self.assertEqual(request, 'request')
  110. self.assertEqual(target, mock_target)
  111. return {'b': value * 10}
  112. patch.replace('abc', action_b)
  113. def action_fail(request, target, value):
  114. self.fail("unrequired action was called")
  115. patch.add('c', action_fail)
  116. patch.remove('c', action_fail)
  117. patch.replace('c', action_fail)
  118. patch_dict = {'id': 123}
  119. patch.dispatch_action(
  120. patch_dict, 'request', mock_target, {
  121. 'op': 'replace',
  122. 'path': 'abc',
  123. 'value': 5,
  124. }
  125. )
  126. self.assertEqual(len(patch_dict), 3)
  127. self.assertEqual(patch_dict['id'], 123)
  128. self.assertEqual(patch_dict['a'], 10)
  129. self.assertEqual(patch_dict['b'], 50)
  130. def test_dispatch(self):
  131. """dispatch calls actions and returns response"""
  132. patch = ApiPatch()
  133. def action_error(request, target, value):
  134. if value == '404':
  135. raise Http404()
  136. if value == 'perm':
  137. raise PermissionDenied("yo ain't doing that!")
  138. patch.replace('error', action_error)
  139. def action_mutate(request, target, value):
  140. return {'value': value * 2}
  141. patch.replace('mutate', action_mutate)
  142. # dispatch requires list as an argument
  143. response = patch.dispatch(MockRequest({}), {})
  144. self.assertEqual(response.status_code, 400)
  145. self.assertEqual(response.data['detail'], "PATCH request should be list of operations")
  146. # valid dispatch
  147. response = patch.dispatch(
  148. MockRequest([
  149. {
  150. 'op': 'replace',
  151. 'path': 'mutate',
  152. 'value': 2,
  153. },
  154. {
  155. 'op': 'replace',
  156. 'path': 'mutate',
  157. 'value': 6,
  158. },
  159. {
  160. 'op': 'replace',
  161. 'path': 'mutate',
  162. 'value': 7,
  163. },
  164. ]), MockObject(13)
  165. )
  166. self.assertEqual(response.status_code, 200)
  167. self.assertEqual(len(response.data['detail']), 3)
  168. self.assertEqual(response.data['detail'][0], 'ok')
  169. self.assertEqual(response.data['detail'][1], 'ok')
  170. self.assertEqual(response.data['detail'][2], 'ok')
  171. self.assertEqual(response.data['id'], 13)
  172. self.assertEqual(response.data['value'], 14)
  173. # invalid action in dispatch
  174. response = patch.dispatch(
  175. MockRequest([
  176. {
  177. 'op': 'replace',
  178. 'path': 'mutate',
  179. 'value': 2,
  180. },
  181. {
  182. 'op': 'replace',
  183. 'path': 'mutate',
  184. 'value': 6,
  185. },
  186. {
  187. 'op': 'replace',
  188. },
  189. {
  190. 'op': 'replace',
  191. 'path': 'mutate',
  192. 'value': 7,
  193. },
  194. ]), MockObject(13)
  195. )
  196. self.assertEqual(response.status_code, 400)
  197. self.assertEqual(len(response.data['detail']), 3)
  198. self.assertEqual(response.data['detail'][0], 'ok')
  199. self.assertEqual(response.data['detail'][1], 'ok')
  200. self.assertEqual(response.data['detail'][2], '"replace" op has to specify path')
  201. self.assertEqual(response.data['id'], 13)
  202. self.assertEqual(response.data['value'], 12)
  203. # action in dispatch raised 404
  204. response = patch.dispatch(
  205. MockRequest([
  206. {
  207. 'op': 'replace',
  208. 'path': 'mutate',
  209. 'value': 2,
  210. },
  211. {
  212. 'op': 'replace',
  213. 'path': 'error',
  214. 'value': '404',
  215. },
  216. {
  217. 'op': 'replace',
  218. 'path': 'mutate',
  219. 'value': 6,
  220. },
  221. {
  222. 'op': 'replace',
  223. 'path': 'mutate',
  224. 'value': 7,
  225. },
  226. ]), MockObject(13)
  227. )
  228. self.assertEqual(response.status_code, 400)
  229. self.assertEqual(len(response.data['detail']), 2)
  230. self.assertEqual(response.data['detail'][0], 'ok')
  231. self.assertEqual(response.data['detail'][1], "NOT FOUND")
  232. self.assertEqual(response.data['id'], 13)
  233. self.assertEqual(response.data['value'], 4)
  234. # action in dispatch raised perm denied
  235. response = patch.dispatch(
  236. MockRequest([
  237. {
  238. 'op': 'replace',
  239. 'path': 'mutate',
  240. 'value': 2,
  241. },
  242. {
  243. 'op': 'replace',
  244. 'path': 'mutate',
  245. 'value': 6,
  246. },
  247. {
  248. 'op': 'replace',
  249. 'path': 'mutate',
  250. 'value': 9,
  251. },
  252. {
  253. 'op': 'replace',
  254. 'path': 'error',
  255. 'value': 'perm',
  256. },
  257. ]), MockObject(13)
  258. )
  259. self.assertEqual(response.status_code, 400)
  260. self.assertEqual(len(response.data['detail']), 4)
  261. self.assertEqual(response.data['detail'][0], 'ok')
  262. self.assertEqual(response.data['detail'][1], 'ok')
  263. self.assertEqual(response.data['detail'][2], 'ok')
  264. self.assertEqual(response.data['detail'][3], "yo ain't doing that!")
  265. self.assertEqual(response.data['id'], 13)
  266. self.assertEqual(response.data['value'], 18)