test_mergeconflict.py 14 KB

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