Browse Source

MergeConflict utility

Rafał Pitoń 7 years ago
parent
commit
23dd21f68c
2 changed files with 135 additions and 8 deletions
  1. 20 6
      misago/threads/mergeconflict.py
  2. 115 2
      misago/threads/tests/test_mergeconflict.py

+ 20 - 6
misago/threads/mergeconflict.py

@@ -112,16 +112,30 @@ class MergeConflict(object):
         return all([i.is_valid() for i in self._handlers])
 
     def raise_exception(self):
+        # if any choice was made by user, we are in validation stage of resolution
+        for conflict in self._conflicts:
+            if self.data.get(conflict.data_name) is not None:
+                self.raise_validation_exception()
+                break
+        else:
+            self.raise_resolutions_exception()
+
+    def raise_validation_exception(self):
         errors = {}
-        for handler in self._conflicts:
-            if self.data.get(handler.data_name) is None:
-                key = '{}s'.format(handler.data_name)
-                errors[key] = handler.get_available_resolutions()
-            elif not handler.is_valid():
-                errors[handler.data_name] = [_("Invalid choice.")]
+        for conflict in self._conflicts:
+            if not conflict.is_valid() or self.data.get(conflict.data_name) is None:
+                errors[conflict.data_name] = [_("Invalid choice.")]
         if errors:
             raise ValidationError(errors)
 
+    def raise_resolutions_exception(self):
+        resolutions = {}
+        for conflict in self._conflicts:
+            key = '{}s'.format(conflict.data_name)
+            resolutions[key] = conflict.get_available_resolutions()
+        if resolutions:
+            raise ValidationError(resolutions)
+
     def get_resolution(self):
         resolved_handlers = [i for i in self._handlers if i.is_valid()]
         return {i.data_name: i.get_resolution() for i in resolved_handlers}

+ 115 - 2
misago/threads/tests/test_mergeconflict.py

@@ -150,8 +150,8 @@ class MergeConflictTests(TestCase):
             'poll': polls[0].poll,
         })
 
-    def test_one_best_answer_three_poll_two_plain_conflict(self):
-        """three threads with best answer, thread with poll and two plain threads conflict"""
+    def test_one_best_answer_three_polls_two_plain_conflict(self):
+        """one thread with best answer, three threads with poll and two plain threads conflict"""
         best_answers = [self.create_best_answer_thread()]
         polls = [self.create_poll_thread() for i in range(3)]
         threads = [
@@ -206,4 +206,117 @@ class MergeConflictTests(TestCase):
         self.assertEqual(merge_conflict.get_resolution(), {
             'best_answer': best_answers[0],
             'poll': None,
+        })
+
+    def test_three_best_answers_three_polls_two_plain_conflict(self):
+        """multiple conflict is handled"""
+        best_answers = [self.create_best_answer_thread() for i in range(3)]
+        polls = [self.create_poll_thread() for i in range(3)]
+        threads = [
+            self.create_plain_thread(),
+            self.create_plain_thread(),
+        ] + best_answers + polls
+
+        merge_conflict = MergeConflict(threads=threads)
+        self.assertTrue(merge_conflict.is_merge_conflict())
+        self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+
+        # without choice, conflict lists all resolutions
+        try:
+            merge_conflict.is_valid(raise_exception=True)
+            self.fail("merge_conflict.is_valid() should raise ValidationError")
+        except ValidationError as e:
+            self.assertTrue(merge_conflict.is_merge_conflict())
+            self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+            self.assertEqual(e.detail, {
+                'best_answers': [['0', 'Unmark all best answers']] + [
+                    [
+                        str(thread.id),
+                        thread.title,
+                    ] for thread in reversed(best_answers)
+                ],
+                'polls': [['0', 'Delete all polls']] + [
+                    [
+                        str(thread.poll.id),
+                        thread.poll.question,
+                    ] for thread in reversed(polls)
+                ]
+            })
+
+        # conflict validates all choices if single choice was given
+        try:
+            merge_conflict = MergeConflict({'best_answer': threads[0].id}, threads)
+            merge_conflict.is_valid(raise_exception=True)
+            self.fail("merge_conflict.is_valid() should raise ValidationError")
+        except ValidationError as e:
+            self.assertTrue(merge_conflict.is_merge_conflict())
+            self.assertEqual(e.detail, {
+                'best_answer': ['Invalid choice.'],
+                'poll': ['Invalid choice.'],
+            })
+
+        try:
+            merge_conflict = MergeConflict({'poll': threads[0].id}, threads)
+            merge_conflict.is_valid(raise_exception=True)
+            self.fail("merge_conflict.is_valid() should raise ValidationError")
+        except ValidationError as e:
+            self.assertTrue(merge_conflict.is_merge_conflict())
+            self.assertEqual(e.detail, {
+                'best_answer': ['Invalid choice.'],
+                'poll': ['Invalid choice.'],
+            })
+
+        # conflict validates all choices if all choices were given
+        try:
+            merge_conflict = MergeConflict({
+                'best_answer': threads[0].id,
+                'poll': threads[0].id,
+            }, threads)
+            merge_conflict.is_valid(raise_exception=True)
+            self.fail("merge_conflict.is_valid() should raise ValidationError")
+        except ValidationError as e:
+            self.assertTrue(merge_conflict.is_merge_conflict())
+            self.assertEqual(e.detail, {
+                'best_answer': ['Invalid choice.'],
+                'poll': ['Invalid choice.'],
+            })
+
+        # conflict returns selected resolutions
+        valid_choices = {'best_answer': best_answers[0].id, 'poll': polls[0].poll.id}
+        merge_conflict = MergeConflict(valid_choices, threads)
+        self.assertTrue(merge_conflict.is_merge_conflict())
+        self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+        merge_conflict.is_valid(raise_exception=True)
+        self.assertEqual(merge_conflict.get_resolution(), {
+            'best_answer': best_answers[0],
+            'poll': polls[0].poll,
+        })
+
+        # conflict returns no-choice resolution
+        merge_conflict = MergeConflict({'best_answer': 0, 'poll': 0}, threads)
+        self.assertTrue(merge_conflict.is_merge_conflict())
+        self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+        merge_conflict.is_valid(raise_exception=True)
+        self.assertEqual(merge_conflict.get_resolution(), {
+            'best_answer': None,
+            'poll': None,
+        })
+
+        # conflict allows mixing no-choice with choice
+        merge_conflict = MergeConflict({'best_answer': best_answers[0].id, 'poll': 0}, threads)
+        self.assertTrue(merge_conflict.is_merge_conflict())
+        self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+        merge_conflict.is_valid(raise_exception=True)
+        self.assertEqual(merge_conflict.get_resolution(), {
+            'best_answer': best_answers[0],
+            'poll': None,
+        })
+
+        merge_conflict = MergeConflict({'best_answer': 0, 'poll': polls[0].poll.id}, threads)
+        self.assertTrue(merge_conflict.is_merge_conflict())
+        self.assertEqual(merge_conflict.get_merge_conflict(), ['best_answer', 'poll'])
+        merge_conflict.is_valid(raise_exception=True)
+        self.assertEqual(merge_conflict.get_resolution(), {
+            'best_answer': None,
+            'poll': polls[0].poll,
         })