test_mergeconflict.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. from django.test import TestCase
  2. from rest_framework.exceptions import ValidationError
  3. from .. import test
  4. from ...categories.models import Category
  5. from ...users.test import create_test_user
  6. from ..mergeconflict import MergeConflict
  7. class MergeConflictTests(TestCase):
  8. def setUp(self):
  9. self.category = Category.objects.get(slug="first-category")
  10. self.user = create_test_user("User", "user@example.com")
  11. def create_plain_thread(self):
  12. return test.post_thread(self.category)
  13. def create_poll_thread(self):
  14. thread = test.post_thread(self.category)
  15. test.post_poll(thread, self.user)
  16. return thread
  17. def create_best_answer_thread(self):
  18. thread = test.post_thread(self.category)
  19. best_answer = test.reply_thread(thread)
  20. thread.set_best_answer(self.user, best_answer)
  21. thread.synchronize()
  22. thread.save()
  23. return thread
  24. def test_plain_threads_no_conflicts(self):
  25. """threads without items of interest don't conflict"""
  26. threads = [self.create_plain_thread() for i in range(10)]
  27. merge_conflict = MergeConflict(threads=threads)
  28. self.assertFalse(merge_conflict.is_merge_conflict())
  29. self.assertEqual(merge_conflict.get_conflicting_fields(), [])
  30. merge_conflict.is_valid(raise_exception=True)
  31. self.assertEqual(merge_conflict.get_resolution(), {})
  32. def test_one_best_answer_one_plain(self):
  33. """thread with best answer and plain thread don't conflict"""
  34. threads = [self.create_best_answer_thread(), self.create_plain_thread()]
  35. merge_conflict = MergeConflict(threads=threads)
  36. self.assertFalse(merge_conflict.is_merge_conflict())
  37. self.assertEqual(merge_conflict.get_conflicting_fields(), [])
  38. merge_conflict.is_valid(raise_exception=True)
  39. self.assertEqual(merge_conflict.get_resolution(), {"best_answer": threads[0]})
  40. def test_one_poll_one_plain(self):
  41. """thread with poll and plain thread don't conflict"""
  42. threads = [self.create_poll_thread(), self.create_plain_thread()]
  43. merge_conflict = MergeConflict(threads=threads)
  44. self.assertFalse(merge_conflict.is_merge_conflict())
  45. self.assertEqual(merge_conflict.get_conflicting_fields(), [])
  46. merge_conflict.is_valid(raise_exception=True)
  47. self.assertEqual(merge_conflict.get_resolution(), {"poll": threads[0].poll})
  48. def test_one_best_answer_one_poll(self):
  49. """thread with best answer and thread with poll don't conflict"""
  50. threads = [self.create_poll_thread(), self.create_best_answer_thread()]
  51. merge_conflict = MergeConflict(threads=threads)
  52. self.assertFalse(merge_conflict.is_merge_conflict())
  53. def test_one_best_answer_one_poll_one_plain(self):
  54. """thread with best answer, thread with poll and plain thread don't conflict"""
  55. threads = [
  56. self.create_plain_thread(),
  57. self.create_poll_thread(),
  58. self.create_best_answer_thread(),
  59. ]
  60. merge_conflict = MergeConflict(threads=threads)
  61. self.assertFalse(merge_conflict.is_merge_conflict())
  62. def test_three_best_answers_one_poll_two_plain_conflict(self):
  63. """
  64. three threads with best answer, thread with poll and two plain threads conflict
  65. """
  66. best_answers = [self.create_best_answer_thread() for i in range(3)]
  67. polls = [self.create_poll_thread()]
  68. threads = (
  69. [self.create_plain_thread(), self.create_plain_thread()]
  70. + best_answers
  71. + polls
  72. )
  73. merge_conflict = MergeConflict(threads=threads)
  74. self.assertTrue(merge_conflict.is_merge_conflict())
  75. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  76. # without choice, conflict lists resolutions
  77. try:
  78. merge_conflict.is_valid(raise_exception=True)
  79. self.fail("merge_conflict.is_valid() should raise ValidationError")
  80. except ValidationError as e:
  81. self.assertTrue(merge_conflict.is_merge_conflict())
  82. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  83. self.assertEqual(
  84. e.detail,
  85. {
  86. "best_answers": [["0", "Unmark all best answers"]]
  87. + [[str(thread.id), thread.title] for thread in best_answers]
  88. },
  89. )
  90. # conflict validates choice
  91. try:
  92. merge_conflict = MergeConflict({"best_answer": threads[0].id}, threads)
  93. merge_conflict.is_valid(raise_exception=True)
  94. self.fail("merge_conflict.is_valid() should raise ValidationError")
  95. except ValidationError as e:
  96. self.assertTrue(merge_conflict.is_merge_conflict())
  97. self.assertEqual(e.detail, {"best_answer": ["Invalid choice."]})
  98. # conflict returns selected resolution
  99. merge_conflict = MergeConflict({"best_answer": best_answers[0].id}, threads)
  100. self.assertTrue(merge_conflict.is_merge_conflict())
  101. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  102. merge_conflict.is_valid(raise_exception=True)
  103. self.assertEqual(
  104. merge_conflict.get_resolution(),
  105. {"best_answer": best_answers[0], "poll": polls[0].poll},
  106. )
  107. # conflict returns no-choice resolution
  108. merge_conflict = MergeConflict({"best_answer": 0}, threads)
  109. self.assertTrue(merge_conflict.is_merge_conflict())
  110. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  111. merge_conflict.is_valid(raise_exception=True)
  112. self.assertEqual(
  113. merge_conflict.get_resolution(),
  114. {"best_answer": None, "poll": polls[0].poll},
  115. )
  116. def test_one_best_answer_three_polls_two_plain_conflict(self):
  117. """
  118. one thread with best answer, three threads with poll
  119. and two plain threads conflict
  120. """
  121. best_answers = [self.create_best_answer_thread()]
  122. polls = [self.create_poll_thread() for i in range(3)]
  123. threads = (
  124. [self.create_plain_thread(), self.create_plain_thread()]
  125. + best_answers
  126. + polls
  127. )
  128. merge_conflict = MergeConflict(threads=threads)
  129. self.assertTrue(merge_conflict.is_merge_conflict())
  130. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  131. # without choice, conflict lists resolutions
  132. try:
  133. merge_conflict.is_valid(raise_exception=True)
  134. self.fail("merge_conflict.is_valid() should raise ValidationError")
  135. except ValidationError as e:
  136. self.assertTrue(merge_conflict.is_merge_conflict())
  137. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  138. self.assertEqual(
  139. e.detail,
  140. {
  141. "polls": [["0", "Delete all polls"]]
  142. + [
  143. [
  144. str(thread.poll.id),
  145. "%s (%s)" % (thread.poll.question, thread.title),
  146. ]
  147. for thread in polls
  148. ]
  149. },
  150. )
  151. # conflict validates choice
  152. try:
  153. merge_conflict = MergeConflict({"poll": threads[0].id}, threads)
  154. merge_conflict.is_valid(raise_exception=True)
  155. self.fail("merge_conflict.is_valid() should raise ValidationError")
  156. except ValidationError as e:
  157. self.assertTrue(merge_conflict.is_merge_conflict())
  158. self.assertEqual(e.detail, {"poll": ["Invalid choice."]})
  159. # conflict returns selected resolution
  160. merge_conflict = MergeConflict({"poll": polls[0].poll.id}, threads)
  161. self.assertTrue(merge_conflict.is_merge_conflict())
  162. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  163. merge_conflict.is_valid(raise_exception=True)
  164. self.assertEqual(
  165. merge_conflict.get_resolution(),
  166. {"best_answer": best_answers[0], "poll": polls[0].poll},
  167. )
  168. # conflict returns no-choice resolution
  169. merge_conflict = MergeConflict({"poll": 0}, threads)
  170. self.assertTrue(merge_conflict.is_merge_conflict())
  171. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  172. merge_conflict.is_valid(raise_exception=True)
  173. self.assertEqual(
  174. merge_conflict.get_resolution(),
  175. {"best_answer": best_answers[0], "poll": None},
  176. )
  177. def test_three_best_answers_three_polls_two_plain_conflict(self):
  178. """multiple conflict is handled"""
  179. best_answers = [self.create_best_answer_thread() for i in range(3)]
  180. polls = [self.create_poll_thread() for i in range(3)]
  181. threads = (
  182. [self.create_plain_thread(), self.create_plain_thread()]
  183. + best_answers
  184. + polls
  185. )
  186. merge_conflict = MergeConflict(threads=threads)
  187. self.assertTrue(merge_conflict.is_merge_conflict())
  188. self.assertEqual(
  189. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  190. )
  191. # without choice, conflict lists all resolutions
  192. try:
  193. merge_conflict.is_valid(raise_exception=True)
  194. self.fail("merge_conflict.is_valid() should raise ValidationError")
  195. except ValidationError as e:
  196. self.assertTrue(merge_conflict.is_merge_conflict())
  197. self.assertEqual(
  198. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  199. )
  200. self.assertEqual(
  201. e.detail,
  202. {
  203. "best_answers": [["0", "Unmark all best answers"]]
  204. + [[str(thread.id), thread.title] for thread in best_answers],
  205. "polls": [["0", "Delete all polls"]]
  206. + [
  207. [
  208. str(thread.poll.id),
  209. "%s (%s)" % (thread.poll.question, thread.title),
  210. ]
  211. for thread in polls
  212. ],
  213. },
  214. )
  215. # conflict validates all choices if single choice was given
  216. try:
  217. merge_conflict = MergeConflict({"best_answer": threads[0].id}, threads)
  218. merge_conflict.is_valid(raise_exception=True)
  219. self.fail("merge_conflict.is_valid() should raise ValidationError")
  220. except ValidationError as e:
  221. self.assertTrue(merge_conflict.is_merge_conflict())
  222. self.assertEqual(
  223. e.detail,
  224. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  225. )
  226. try:
  227. merge_conflict = MergeConflict({"poll": threads[0].id}, threads)
  228. merge_conflict.is_valid(raise_exception=True)
  229. self.fail("merge_conflict.is_valid() should raise ValidationError")
  230. except ValidationError as e:
  231. self.assertTrue(merge_conflict.is_merge_conflict())
  232. self.assertEqual(
  233. e.detail,
  234. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  235. )
  236. # conflict validates all choices if all choices were given
  237. try:
  238. merge_conflict = MergeConflict(
  239. {"best_answer": threads[0].id, "poll": threads[0].id}, threads
  240. )
  241. merge_conflict.is_valid(raise_exception=True)
  242. self.fail("merge_conflict.is_valid() should raise ValidationError")
  243. except ValidationError as e:
  244. self.assertTrue(merge_conflict.is_merge_conflict())
  245. self.assertEqual(
  246. e.detail,
  247. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  248. )
  249. # conflict returns selected resolutions
  250. valid_choices = {"best_answer": best_answers[0].id, "poll": polls[0].poll.id}
  251. merge_conflict = MergeConflict(valid_choices, threads)
  252. self.assertTrue(merge_conflict.is_merge_conflict())
  253. self.assertEqual(
  254. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  255. )
  256. merge_conflict.is_valid(raise_exception=True)
  257. self.assertEqual(
  258. merge_conflict.get_resolution(),
  259. {"best_answer": best_answers[0], "poll": polls[0].poll},
  260. )
  261. # conflict returns no-choice resolution
  262. merge_conflict = MergeConflict({"best_answer": 0, "poll": 0}, threads)
  263. self.assertTrue(merge_conflict.is_merge_conflict())
  264. self.assertEqual(
  265. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  266. )
  267. merge_conflict.is_valid(raise_exception=True)
  268. self.assertEqual(
  269. merge_conflict.get_resolution(), {"best_answer": None, "poll": None}
  270. )
  271. # conflict allows mixing no-choice with choice
  272. merge_conflict = MergeConflict(
  273. {"best_answer": best_answers[0].id, "poll": 0}, threads
  274. )
  275. self.assertTrue(merge_conflict.is_merge_conflict())
  276. self.assertEqual(
  277. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  278. )
  279. merge_conflict.is_valid(raise_exception=True)
  280. self.assertEqual(
  281. merge_conflict.get_resolution(),
  282. {"best_answer": best_answers[0], "poll": None},
  283. )
  284. merge_conflict = MergeConflict(
  285. {"best_answer": 0, "poll": polls[0].poll.id}, threads
  286. )
  287. self.assertTrue(merge_conflict.is_merge_conflict())
  288. self.assertEqual(
  289. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  290. )
  291. merge_conflict.is_valid(raise_exception=True)
  292. self.assertEqual(
  293. merge_conflict.get_resolution(),
  294. {"best_answer": None, "poll": polls[0].poll},
  295. )