test_apipatch.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. {"op": "add", "path": "test", "value": 42},
  48. {"op": "remove", "path": "other-test", "value": "Lorem"},
  49. {"op": "replace", "path": "false-test", "value": None},
  50. ]
  51. for action in VALID_ACTIONS:
  52. patch.validate_action(action)
  53. # undefined op
  54. UNSUPPORTED_ACTIONS = ({}, {"op": ""}, {"no": "op"})
  55. for action in UNSUPPORTED_ACTIONS:
  56. try:
  57. patch.validate_action(action)
  58. except InvalidAction as e:
  59. self.assertEqual(e.args[0], "undefined op")
  60. # unsupported op
  61. try:
  62. patch.validate_action({"op": "nope"})
  63. except InvalidAction as e:
  64. self.assertEqual(e.args[0], '"nope" op is unsupported')
  65. # op lacking patch
  66. try:
  67. patch.validate_action({"op": "add"})
  68. except InvalidAction as e:
  69. self.assertEqual(e.args[0], '"add" op has to specify path')
  70. # op lacking value
  71. try:
  72. patch.validate_action({"op": "add", "path": "yolo"})
  73. except InvalidAction as e:
  74. self.assertEqual(e.args[0], '"add" op has to specify value')
  75. # empty value is allowed
  76. try:
  77. patch.validate_action({"op": "add", "path": "yolo", "value": ""})
  78. except InvalidAction as e:
  79. self.assertEqual(e.args[0], '"add" op has to specify value')
  80. def test_dispatch_action(self):
  81. """dispatch_action calls specified actions"""
  82. patch = ApiPatch()
  83. mock_target = MockObject(13)
  84. def action_a(request, target, value):
  85. self.assertEqual(request, "request")
  86. self.assertEqual(target, mock_target)
  87. return {"a": value * 2, "b": 111}
  88. patch.replace("abc", action_a)
  89. def action_b(request, target, value):
  90. self.assertEqual(request, "request")
  91. self.assertEqual(target, mock_target)
  92. return {"b": value * 10}
  93. patch.replace("abc", action_b)
  94. def action_fail(request, target, value):
  95. self.fail("unrequired action was called")
  96. patch.add("c", action_fail)
  97. patch.remove("c", action_fail)
  98. patch.replace("c", action_fail)
  99. patch_dict = {"id": 123}
  100. patch.dispatch_action(
  101. patch_dict,
  102. "request",
  103. mock_target,
  104. {"op": "replace", "path": "abc", "value": 5},
  105. )
  106. self.assertEqual(len(patch_dict), 3)
  107. self.assertEqual(patch_dict["id"], 123)
  108. self.assertEqual(patch_dict["a"], 10)
  109. self.assertEqual(patch_dict["b"], 50)
  110. def test_dispatch(self):
  111. """dispatch calls actions and returns response"""
  112. patch = ApiPatch()
  113. def action_error(request, target, value):
  114. if value == "404":
  115. raise Http404()
  116. if value == "perm":
  117. raise PermissionDenied("yo ain't doing that!")
  118. patch.replace("error", action_error)
  119. def action_mutate(request, target, value):
  120. return {"value": value * 2}
  121. patch.replace("mutate", action_mutate)
  122. # dispatch requires list as an argument
  123. response = patch.dispatch(MockRequest({}), {})
  124. self.assertEqual(response.status_code, 400)
  125. self.assertEqual(
  126. response.data["detail"], "PATCH request should be list of operations"
  127. )
  128. # valid dispatch
  129. response = patch.dispatch(
  130. MockRequest(
  131. [
  132. {"op": "replace", "path": "mutate", "value": 2},
  133. {"op": "replace", "path": "mutate", "value": 6},
  134. {"op": "replace", "path": "mutate", "value": 7},
  135. ]
  136. ),
  137. MockObject(13),
  138. )
  139. self.assertEqual(response.status_code, 200)
  140. self.assertEqual(len(response.data["detail"]), 3)
  141. self.assertEqual(response.data["detail"][0], "ok")
  142. self.assertEqual(response.data["detail"][1], "ok")
  143. self.assertEqual(response.data["detail"][2], "ok")
  144. self.assertEqual(response.data["id"], 13)
  145. self.assertEqual(response.data["value"], 14)
  146. # invalid action in dispatch
  147. response = patch.dispatch(
  148. MockRequest(
  149. [
  150. {"op": "replace", "path": "mutate", "value": 2},
  151. {"op": "replace", "path": "mutate", "value": 6},
  152. {"op": "replace"},
  153. {"op": "replace", "path": "mutate", "value": 7},
  154. ]
  155. ),
  156. MockObject(13),
  157. )
  158. self.assertEqual(response.status_code, 400)
  159. self.assertEqual(len(response.data["detail"]), 3)
  160. self.assertEqual(response.data["detail"][0], "ok")
  161. self.assertEqual(response.data["detail"][1], "ok")
  162. self.assertEqual(response.data["detail"][2], '"replace" op has to specify path')
  163. self.assertEqual(response.data["id"], 13)
  164. self.assertEqual(response.data["value"], 12)
  165. # action in dispatch raised 404
  166. response = patch.dispatch(
  167. MockRequest(
  168. [
  169. {"op": "replace", "path": "mutate", "value": 2},
  170. {"op": "replace", "path": "error", "value": "404"},
  171. {"op": "replace", "path": "mutate", "value": 6},
  172. {"op": "replace", "path": "mutate", "value": 7},
  173. ]
  174. ),
  175. MockObject(13),
  176. )
  177. self.assertEqual(response.status_code, 400)
  178. self.assertEqual(len(response.data["detail"]), 2)
  179. self.assertEqual(response.data["detail"][0], "ok")
  180. self.assertEqual(response.data["detail"][1], "NOT FOUND")
  181. self.assertEqual(response.data["id"], 13)
  182. self.assertEqual(response.data["value"], 4)
  183. # action in dispatch raised perm denied
  184. response = patch.dispatch(
  185. MockRequest(
  186. [
  187. {"op": "replace", "path": "mutate", "value": 2},
  188. {"op": "replace", "path": "mutate", "value": 6},
  189. {"op": "replace", "path": "mutate", "value": 9},
  190. {"op": "replace", "path": "error", "value": "perm"},
  191. ]
  192. ),
  193. MockObject(13),
  194. )
  195. self.assertEqual(response.status_code, 400)
  196. self.assertEqual(len(response.data["detail"]), 4)
  197. self.assertEqual(response.data["detail"][0], "ok")
  198. self.assertEqual(response.data["detail"][1], "ok")
  199. self.assertEqual(response.data["detail"][2], "ok")
  200. self.assertEqual(response.data["detail"][3], "yo ain't doing that!")
  201. self.assertEqual(response.data["id"], 13)
  202. self.assertEqual(response.data["value"], 18)