test_mergeconflict.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. """three threads with best answer, thread with poll and two plain threads conflict"""
  64. best_answers = [self.create_best_answer_thread() for i in range(3)]
  65. polls = [self.create_poll_thread()]
  66. threads = (
  67. [self.create_plain_thread(), self.create_plain_thread()]
  68. + best_answers
  69. + polls
  70. )
  71. merge_conflict = MergeConflict(threads=threads)
  72. self.assertTrue(merge_conflict.is_merge_conflict())
  73. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  74. # without choice, conflict lists resolutions
  75. try:
  76. merge_conflict.is_valid(raise_exception=True)
  77. self.fail("merge_conflict.is_valid() should raise ValidationError")
  78. except ValidationError as e:
  79. self.assertTrue(merge_conflict.is_merge_conflict())
  80. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  81. self.assertEqual(
  82. e.detail,
  83. {
  84. "best_answers": [["0", "Unmark all best answers"]]
  85. + [[str(thread.id), thread.title] for thread in best_answers]
  86. },
  87. )
  88. # conflict validates choice
  89. try:
  90. merge_conflict = MergeConflict({"best_answer": threads[0].id}, threads)
  91. merge_conflict.is_valid(raise_exception=True)
  92. self.fail("merge_conflict.is_valid() should raise ValidationError")
  93. except ValidationError as e:
  94. self.assertTrue(merge_conflict.is_merge_conflict())
  95. self.assertEqual(e.detail, {"best_answer": ["Invalid choice."]})
  96. # conflict returns selected resolution
  97. merge_conflict = MergeConflict({"best_answer": best_answers[0].id}, threads)
  98. self.assertTrue(merge_conflict.is_merge_conflict())
  99. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  100. merge_conflict.is_valid(raise_exception=True)
  101. self.assertEqual(
  102. merge_conflict.get_resolution(),
  103. {"best_answer": best_answers[0], "poll": polls[0].poll},
  104. )
  105. # conflict returns no-choice resolution
  106. merge_conflict = MergeConflict({"best_answer": 0}, threads)
  107. self.assertTrue(merge_conflict.is_merge_conflict())
  108. self.assertEqual(merge_conflict.get_conflicting_fields(), ["best_answer"])
  109. merge_conflict.is_valid(raise_exception=True)
  110. self.assertEqual(
  111. merge_conflict.get_resolution(),
  112. {"best_answer": None, "poll": polls[0].poll},
  113. )
  114. def test_one_best_answer_three_polls_two_plain_conflict(self):
  115. """one thread with best answer, three threads with poll and two plain threads conflict"""
  116. best_answers = [self.create_best_answer_thread()]
  117. polls = [self.create_poll_thread() for i in range(3)]
  118. threads = (
  119. [self.create_plain_thread(), self.create_plain_thread()]
  120. + best_answers
  121. + polls
  122. )
  123. merge_conflict = MergeConflict(threads=threads)
  124. self.assertTrue(merge_conflict.is_merge_conflict())
  125. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  126. # without choice, conflict lists resolutions
  127. try:
  128. merge_conflict.is_valid(raise_exception=True)
  129. self.fail("merge_conflict.is_valid() should raise ValidationError")
  130. except ValidationError as e:
  131. self.assertTrue(merge_conflict.is_merge_conflict())
  132. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  133. self.assertEqual(
  134. e.detail,
  135. {
  136. "polls": [["0", "Delete all polls"]]
  137. + [
  138. [
  139. str(thread.poll.id),
  140. "%s (%s)" % (thread.poll.question, thread.title),
  141. ]
  142. for thread in polls
  143. ]
  144. },
  145. )
  146. # conflict validates choice
  147. try:
  148. merge_conflict = MergeConflict({"poll": threads[0].id}, threads)
  149. merge_conflict.is_valid(raise_exception=True)
  150. self.fail("merge_conflict.is_valid() should raise ValidationError")
  151. except ValidationError as e:
  152. self.assertTrue(merge_conflict.is_merge_conflict())
  153. self.assertEqual(e.detail, {"poll": ["Invalid choice."]})
  154. # conflict returns selected resolution
  155. merge_conflict = MergeConflict({"poll": polls[0].poll.id}, threads)
  156. self.assertTrue(merge_conflict.is_merge_conflict())
  157. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  158. merge_conflict.is_valid(raise_exception=True)
  159. self.assertEqual(
  160. merge_conflict.get_resolution(),
  161. {"best_answer": best_answers[0], "poll": polls[0].poll},
  162. )
  163. # conflict returns no-choice resolution
  164. merge_conflict = MergeConflict({"poll": 0}, threads)
  165. self.assertTrue(merge_conflict.is_merge_conflict())
  166. self.assertEqual(merge_conflict.get_conflicting_fields(), ["poll"])
  167. merge_conflict.is_valid(raise_exception=True)
  168. self.assertEqual(
  169. merge_conflict.get_resolution(),
  170. {"best_answer": best_answers[0], "poll": None},
  171. )
  172. def test_three_best_answers_three_polls_two_plain_conflict(self):
  173. """multiple conflict is handled"""
  174. best_answers = [self.create_best_answer_thread() for i in range(3)]
  175. polls = [self.create_poll_thread() for i in range(3)]
  176. threads = (
  177. [self.create_plain_thread(), self.create_plain_thread()]
  178. + best_answers
  179. + polls
  180. )
  181. merge_conflict = MergeConflict(threads=threads)
  182. self.assertTrue(merge_conflict.is_merge_conflict())
  183. self.assertEqual(
  184. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  185. )
  186. # without choice, conflict lists all resolutions
  187. try:
  188. merge_conflict.is_valid(raise_exception=True)
  189. self.fail("merge_conflict.is_valid() should raise ValidationError")
  190. except ValidationError as e:
  191. self.assertTrue(merge_conflict.is_merge_conflict())
  192. self.assertEqual(
  193. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  194. )
  195. self.assertEqual(
  196. e.detail,
  197. {
  198. "best_answers": [["0", "Unmark all best answers"]]
  199. + [[str(thread.id), thread.title] for thread in best_answers],
  200. "polls": [["0", "Delete all polls"]]
  201. + [
  202. [
  203. str(thread.poll.id),
  204. "%s (%s)" % (thread.poll.question, thread.title),
  205. ]
  206. for thread in polls
  207. ],
  208. },
  209. )
  210. # conflict validates all choices if single choice was given
  211. try:
  212. merge_conflict = MergeConflict({"best_answer": threads[0].id}, threads)
  213. merge_conflict.is_valid(raise_exception=True)
  214. self.fail("merge_conflict.is_valid() should raise ValidationError")
  215. except ValidationError as e:
  216. self.assertTrue(merge_conflict.is_merge_conflict())
  217. self.assertEqual(
  218. e.detail,
  219. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  220. )
  221. try:
  222. merge_conflict = MergeConflict({"poll": threads[0].id}, threads)
  223. merge_conflict.is_valid(raise_exception=True)
  224. self.fail("merge_conflict.is_valid() should raise ValidationError")
  225. except ValidationError as e:
  226. self.assertTrue(merge_conflict.is_merge_conflict())
  227. self.assertEqual(
  228. e.detail,
  229. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  230. )
  231. # conflict validates all choices if all choices were given
  232. try:
  233. merge_conflict = MergeConflict(
  234. {"best_answer": threads[0].id, "poll": threads[0].id}, threads
  235. )
  236. merge_conflict.is_valid(raise_exception=True)
  237. self.fail("merge_conflict.is_valid() should raise ValidationError")
  238. except ValidationError as e:
  239. self.assertTrue(merge_conflict.is_merge_conflict())
  240. self.assertEqual(
  241. e.detail,
  242. {"best_answer": ["Invalid choice."], "poll": ["Invalid choice."]},
  243. )
  244. # conflict returns selected resolutions
  245. valid_choices = {"best_answer": best_answers[0].id, "poll": polls[0].poll.id}
  246. merge_conflict = MergeConflict(valid_choices, threads)
  247. self.assertTrue(merge_conflict.is_merge_conflict())
  248. self.assertEqual(
  249. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  250. )
  251. merge_conflict.is_valid(raise_exception=True)
  252. self.assertEqual(
  253. merge_conflict.get_resolution(),
  254. {"best_answer": best_answers[0], "poll": polls[0].poll},
  255. )
  256. # conflict returns no-choice resolution
  257. merge_conflict = MergeConflict({"best_answer": 0, "poll": 0}, threads)
  258. self.assertTrue(merge_conflict.is_merge_conflict())
  259. self.assertEqual(
  260. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  261. )
  262. merge_conflict.is_valid(raise_exception=True)
  263. self.assertEqual(
  264. merge_conflict.get_resolution(), {"best_answer": None, "poll": None}
  265. )
  266. # conflict allows mixing no-choice with choice
  267. merge_conflict = MergeConflict(
  268. {"best_answer": best_answers[0].id, "poll": 0}, threads
  269. )
  270. self.assertTrue(merge_conflict.is_merge_conflict())
  271. self.assertEqual(
  272. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  273. )
  274. merge_conflict.is_valid(raise_exception=True)
  275. self.assertEqual(
  276. merge_conflict.get_resolution(),
  277. {"best_answer": best_answers[0], "poll": None},
  278. )
  279. merge_conflict = MergeConflict(
  280. {"best_answer": 0, "poll": polls[0].poll.id}, threads
  281. )
  282. self.assertTrue(merge_conflict.is_merge_conflict())
  283. self.assertEqual(
  284. merge_conflict.get_conflicting_fields(), ["best_answer", "poll"]
  285. )
  286. merge_conflict.is_valid(raise_exception=True)
  287. self.assertEqual(
  288. merge_conflict.get_resolution(),
  289. {"best_answer": None, "poll": polls[0].poll},
  290. )