Repository: python-discord/bot

Issue:
Should the syncer confirmation be removed?
The current threshold is way too low, but some of us have doubts on its usefulness. The `wait_until_guild_available` solution should prevent the cache from ever being empty. It even explicitly checks that the cache isn't empty and avoids setting the event. That being said, there may be some strange situation in which the bot receives a partial cache from Discord.

If it's not removed, then the thresholds should at least be increased. I also had the idea of only applying the size check to the deleted diffs and not the created or updated ones. What would be good thresholds for roles and users? I think it's currently set to 10 for both. 10 is probably good for deleted roles, but for users, it'd need to be somewhere in the thousands considering we have 70k members.


Discussion:
@SebastiaanZ Any thoughts, since you proposed the confirmation?
I think we _should_ remove this. I think it's been made irrelevant by `wait_until_guild_available`.
Thanks. I'd like input from a few more people before actioning it, especially from @python-discord/core-developers.
I'm all for removal of this feature.

Honestly, with you having solved the actual cause of the issue, I don't really see this feature coming into play for legitimate reasons. If something funky does happen in the future, we'll just have to a big sync instead. The frequent sync issues we've had no longer occur, though, so it won't be often if ever.
Okay, it's been a few days and I got 2 "yes" responses. I'm going to start on this.

Resolving patch:
diff --git a/bot/constants.py b/bot/constants.py
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -560,13 +560,6 @@ class RedirectOutput(metaclass=YAMLGetter):
     delete_delay: int
 
 
-class Sync(metaclass=YAMLGetter):
-    section = 'sync'
-
-    confirm_timeout: int
-    max_diff: int
-
-
 class PythonNews(metaclass=YAMLGetter):
     section = 'python_news'
 
diff --git a/bot/exts/backend/sync/_syncers.py b/bot/exts/backend/sync/_syncers.py
--- a/bot/exts/backend/sync/_syncers.py
+++ b/bot/exts/backend/sync/_syncers.py
@@ -1,15 +1,11 @@
 import abc
-import asyncio
 import logging
 import typing as t
 from collections import namedtuple
-from functools import partial
 
-import discord
-from discord import Guild, HTTPException, Member, Message, Reaction, User
+from discord import Guild
 from discord.ext.commands import Context
 
-from bot import constants
 from bot.api import ResponseCodeError
 from bot.bot import Bot
 
@@ -25,9 +21,6 @@
 class Syncer(abc.ABC):
     """Base class for synchronising the database with objects in the Discord cache."""
 
-    _CORE_DEV_MENTION = f"<@&{constants.Roles.core_developers}> "
-    _REACTION_EMOJIS = (constants.Emojis.check_mark, constants.Emojis.cross_mark)
-
     def __init__(self, bot: Bot) -> None:
         self.bot = bot
 
@@ -37,112 +30,6 @@ def name(self) -> str:
         """The name of the syncer; used in output messages and logging."""
         raise NotImplementedError  # pragma: no cover
 
-    async def _send_prompt(self, message: t.Optional[Message] = None) -> t.Optional[Message]:
-        """
-        Send a prompt to confirm or abort a sync using reactions and return the sent message.
-
-        If a message is given, it is edited to display the prompt and reactions. Otherwise, a new
-        message is sent to the dev-core channel and mentions the core developers role. If the
-        channel cannot be retrieved, return None.
-        """
-        log.trace(f"Sending {self.name} sync confirmation prompt.")
-
-        msg_content = (
-            f'Possible cache issue while syncing {self.name}s. '
-            f'More than {constants.Sync.max_diff} {self.name}s were changed. '
-            f'React to confirm or abort the sync.'
-        )
-
-        # Send to core developers if it's an automatic sync.
-        if not message:
-            log.trace("Message not provided for confirmation; creating a new one in dev-core.")
-            channel = self.bot.get_channel(constants.Channels.dev_core)
-
-            if not channel:
-                log.debug("Failed to get the dev-core channel from cache; attempting to fetch it.")
-                try:
-                    channel = await self.bot.fetch_channel(constants.Channels.dev_core)
-                except HTTPException:
-                    log.exception(
-                        f"Failed to fetch channel for sending sync confirmation prompt; "
-                        f"aborting {self.name} sync."
-                    )
-                    return None
-
-            allowed_roles = [discord.Object(constants.Roles.core_developers)]
-            message = await channel.send(
-                f"{self._CORE_DEV_MENTION}{msg_content}",
-                allowed_mentions=discord.AllowedMentions(everyone=False, roles=allowed_roles)
-            )
-        else:
-            await message.edit(content=msg_content)
-
-        # Add the initial reactions.
-        log.trace(f"Adding reactions to {self.name} syncer confirmation prompt.")
-        for emoji in self._REACTION_EMOJIS:
-            await message.add_reaction(emoji)
-
-        return message
-
-    def _reaction_check(
-        self,
-        author: Member,
-        message: Message,
-        reaction: Reaction,
-        user: t.Union[Member, User]
-    ) -> bool:
-        """
-        Return True if the `reaction` is a valid confirmation or abort reaction on `message`.
-
-        If the `author` of the prompt is a bot, then a reaction by any core developer will be
-        considered valid. Otherwise, the author of the reaction (`user`) will have to be the
-        `author` of the prompt.
-        """
-        # For automatic syncs, check for the core dev role instead of an exact author
-        has_role = any(constants.Roles.core_developers == role.id for role in user.roles)
-        return (
-            reaction.message.id == message.id
-            and not user.bot
-            and (has_role if author.bot else user == author)
-            and str(reaction.emoji) in self._REACTION_EMOJIS
-        )
-
-    async def _wait_for_confirmation(self, author: Member, message: Message) -> bool:
-        """
-        Wait for a confirmation reaction by `author` on `message` and return True if confirmed.
-
-        Uses the `_reaction_check` function to determine if a reaction is valid.
-
-        If there is no reaction within `bot.constants.Sync.confirm_timeout` seconds, return False.
-        To acknowledge the reaction (or lack thereof), `message` will be edited.
-        """
-        # Preserve the core-dev role mention in the message edits so users aren't confused about
-        # where notifications came from.
-        mention = self._CORE_DEV_MENTION if author.bot else ""
-
-        reaction = None
-        try:
-            log.trace(f"Waiting for a reaction to the {self.name} syncer confirmation prompt.")
-            reaction, _ = await self.bot.wait_for(
-                'reaction_add',
-                check=partial(self._reaction_check, author, message),
-                timeout=constants.Sync.confirm_timeout
-            )
-        except asyncio.TimeoutError:
-            # reaction will remain none thus sync will be aborted in the finally block below.
-            log.debug(f"The {self.name} syncer confirmation prompt timed out.")
-
-        if str(reaction) == constants.Emojis.check_mark:
-            log.trace(f"The {self.name} syncer was confirmed.")
-            await message.edit(content=f':ok_hand: {mention}{self.name} sync will proceed.')
-            return True
-        else:
-            log.info(f"The {self.name} syncer was aborted or timed out!")
-            await message.edit(
-                content=f':warning: {mention}{self.name} sync aborted or timed out!'
-            )
-            return False
-
     @abc.abstractmethod
     async def _get_diff(self, guild: Guild) -> _Diff:
         """Return the difference between the cache of `guild` and the database."""
@@ -153,62 +40,19 @@ async def _sync(self, diff: _Diff) -> None:
         """Perform the API calls for synchronisation."""
         raise NotImplementedError  # pragma: no cover
 
-    async def _get_confirmation_result(
-        self,
-        diff_size: int,
-        author: Member,
-        message: t.Optional[Message] = None
-    ) -> t.Tuple[bool, t.Optional[Message]]:
-        """
-        Prompt for confirmation and return a tuple of the result and the prompt message.
-
-        `diff_size` is the size of the diff of the sync. If it is greater than
-        `bot.constants.Sync.max_diff`, the prompt will be sent. The `author` is the invoked of the
-        sync and the `message` is an extant message to edit to display the prompt.
-
-        If confirmed or no confirmation was needed, the result is True. The returned message will
-        either be the given `message` or a new one which was created when sending the prompt.
-        """
-        log.trace(f"Determining if confirmation prompt should be sent for {self.name} syncer.")
-        if diff_size > constants.Sync.max_diff:
-            message = await self._send_prompt(message)
-            if not message:
-                return False, None  # Couldn't get channel.
-
-            confirmed = await self._wait_for_confirmation(author, message)
-            if not confirmed:
-                return False, message  # Sync aborted.
-
-        return True, message
-
     async def sync(self, guild: Guild, ctx: t.Optional[Context] = None) -> None:
         """
         Synchronise the database with the cache of `guild`.
 
-        If the differences between the cache and the database are greater than
-        `bot.constants.Sync.max_diff`, then a confirmation prompt will be sent to the dev-core
-        channel. The confirmation can be optionally redirect to `ctx` instead.
+        If `ctx` is given, send a message with the results.
         """
         log.info(f"Starting {self.name} syncer.")
 
-        message = None
-        author = self.bot.user
         if ctx:
             message = await ctx.send(f"📊 Synchronising {self.name}s.")
-            author = ctx.author
-
+        else:
+            message = None
         diff = await self._get_diff(guild)
-        diff_dict = diff._asdict()  # Ugly method for transforming the NamedTuple into a dict
-        totals = {k: len(v) for k, v in diff_dict.items() if v is not None}
-        diff_size = sum(totals.values())
-
-        confirmed, message = await self._get_confirmation_result(diff_size, author, message)
-        if not confirmed:
-            return
-
-        # Preserve the core-dev role mention in the message edits so users aren't confused about
-        # where notifications came from.
-        mention = self._CORE_DEV_MENTION if author.bot else ""
 
         try:
             await self._sync(diff)
@@ -217,11 +61,14 @@ async def sync(self, guild: Guild, ctx: t.Optional[Context] = None) -> None:
 
             # Don't show response text because it's probably some really long HTML.
             results = f"status {e.status}\n```{e.response_json or 'See log output for details'}```"
-            content = f":x: {mention}Synchronisation of {self.name}s failed: {results}"
+            content = f":x: Synchronisation of {self.name}s failed: {results}"
         else:
-            results = ", ".join(f"{name} `{total}`" for name, total in totals.items())
+            diff_dict = diff._asdict()
+            results = (f"{name} `{len(val)}`" for name, val in diff_dict.items() if val is not None)
+            results = ", ".join(results)
+
             log.info(f"{self.name} syncer finished: {results}.")
-            content = f":ok_hand: {mention}Synchronisation of {self.name}s complete: {results}"
+            content = f":ok_hand: Synchronisation of {self.name}s complete: {results}"
 
         if message:
             await message.edit(content=content)


Test patch:
diff --git a/tests/bot/exts/backend/sync/test_base.py b/tests/bot/exts/backend/sync/test_base.py
--- a/tests/bot/exts/backend/sync/test_base.py
+++ b/tests/bot/exts/backend/sync/test_base.py
@@ -1,12 +1,9 @@
-import asyncio
 import unittest
 from unittest import mock
 
-import discord
 
-from bot import constants
 from bot.api import ResponseCodeError
-from bot.exts.backend.sync._syncers import Syncer, _Diff
+from bot.exts.backend.sync._syncers import Syncer
 from tests import helpers
 
 
@@ -30,280 +27,16 @@ def test_instantiation_fails_without_abstract_methods(self):
             Syncer(self.bot)
 
 
-class SyncerSendPromptTests(unittest.IsolatedAsyncioTestCase):
-    """Tests for sending the sync confirmation prompt."""
-
-    def setUp(self):
-        self.bot = helpers.MockBot()
-        self.syncer = TestSyncer(self.bot)
-
-    def mock_get_channel(self):
-        """Fixture to return a mock channel and message for when `get_channel` is used."""
-        self.bot.reset_mock()
-
-        mock_channel = helpers.MockTextChannel()
-        mock_message = helpers.MockMessage()
-
-        mock_channel.send.return_value = mock_message
-        self.bot.get_channel.return_value = mock_channel
-
-        return mock_channel, mock_message
-
-    def mock_fetch_channel(self):
-        """Fixture to return a mock channel and message for when `fetch_channel` is used."""
-        self.bot.reset_mock()
-
-        mock_channel = helpers.MockTextChannel()
-        mock_message = helpers.MockMessage()
-
-        self.bot.get_channel.return_value = None
-        mock_channel.send.return_value = mock_message
-        self.bot.fetch_channel.return_value = mock_channel
-
-        return mock_channel, mock_message
-
-    async def test_send_prompt_edits_and_returns_message(self):
-        """The given message should be edited to display the prompt and then should be returned."""
-        msg = helpers.MockMessage()
-        ret_val = await self.syncer._send_prompt(msg)
-
-        msg.edit.assert_called_once()
-        self.assertIn("content", msg.edit.call_args[1])
-        self.assertEqual(ret_val, msg)
-
-    async def test_send_prompt_gets_dev_core_channel(self):
-        """The dev-core channel should be retrieved if an extant message isn't given."""
-        subtests = (
-            (self.bot.get_channel, self.mock_get_channel),
-            (self.bot.fetch_channel, self.mock_fetch_channel),
-        )
-
-        for method, mock_ in subtests:
-            with self.subTest(method=method, msg=mock_.__name__):
-                mock_()
-                await self.syncer._send_prompt()
-
-                method.assert_called_once_with(constants.Channels.dev_core)
-
-    async def test_send_prompt_returns_none_if_channel_fetch_fails(self):
-        """None should be returned if there's an HTTPException when fetching the channel."""
-        self.bot.get_channel.return_value = None
-        self.bot.fetch_channel.side_effect = discord.HTTPException(mock.MagicMock(), "test error!")
-
-        ret_val = await self.syncer._send_prompt()
-
-        self.assertIsNone(ret_val)
-
-    async def test_send_prompt_sends_and_returns_new_message_if_not_given(self):
-        """A new message mentioning core devs should be sent and returned if message isn't given."""
-        for mock_ in (self.mock_get_channel, self.mock_fetch_channel):
-            with self.subTest(msg=mock_.__name__):
-                mock_channel, mock_message = mock_()
-                ret_val = await self.syncer._send_prompt()
-
-                mock_channel.send.assert_called_once()
-                self.assertIn(self.syncer._CORE_DEV_MENTION, mock_channel.send.call_args[0][0])
-                self.assertEqual(ret_val, mock_message)
-
-    async def test_send_prompt_adds_reactions(self):
-        """The message should have reactions for confirmation added."""
-        extant_message = helpers.MockMessage()
-        subtests = (
-            (extant_message, lambda: (None, extant_message)),
-            (None, self.mock_get_channel),
-            (None, self.mock_fetch_channel),
-        )
-
-        for message_arg, mock_ in subtests:
-            subtest_msg = "Extant message" if mock_.__name__ == "<lambda>" else mock_.__name__
-
-            with self.subTest(msg=subtest_msg):
-                _, mock_message = mock_()
-                await self.syncer._send_prompt(message_arg)
-
-                calls = [mock.call(emoji) for emoji in self.syncer._REACTION_EMOJIS]
-                mock_message.add_reaction.assert_has_calls(calls)
-
-
-class SyncerConfirmationTests(unittest.IsolatedAsyncioTestCase):
-    """Tests for waiting for a sync confirmation reaction on the prompt."""
-
-    def setUp(self):
-        self.bot = helpers.MockBot()
-        self.syncer = TestSyncer(self.bot)
-        self.core_dev_role = helpers.MockRole(id=constants.Roles.core_developers)
-
-    @staticmethod
-    def get_message_reaction(emoji):
-        """Fixture to return a mock message an reaction from the given `emoji`."""
-        message = helpers.MockMessage()
-        reaction = helpers.MockReaction(emoji=emoji, message=message)
-
-        return message, reaction
-
-    def test_reaction_check_for_valid_emoji_and_authors(self):
-        """Should return True if authors are identical or are a bot and a core dev, respectively."""
-        user_subtests = (
-            (
-                helpers.MockMember(id=77),
-                helpers.MockMember(id=77),
-                "identical users",
-            ),
-            (
-                helpers.MockMember(id=77, bot=True),
-                helpers.MockMember(id=43, roles=[self.core_dev_role]),
-                "bot author and core-dev reactor",
-            ),
-        )
-
-        for emoji in self.syncer._REACTION_EMOJIS:
-            for author, user, msg in user_subtests:
-                with self.subTest(author=author, user=user, emoji=emoji, msg=msg):
-                    message, reaction = self.get_message_reaction(emoji)
-                    ret_val = self.syncer._reaction_check(author, message, reaction, user)
-
-                    self.assertTrue(ret_val)
-
-    def test_reaction_check_for_invalid_reactions(self):
-        """Should return False for invalid reaction events."""
-        valid_emoji = self.syncer._REACTION_EMOJIS[0]
-        subtests = (
-            (
-                helpers.MockMember(id=77),
-                *self.get_message_reaction(valid_emoji),
-                helpers.MockMember(id=43, roles=[self.core_dev_role]),
-                "users are not identical",
-            ),
-            (
-                helpers.MockMember(id=77, bot=True),
-                *self.get_message_reaction(valid_emoji),
-                helpers.MockMember(id=43),
-                "reactor lacks the core-dev role",
-            ),
-            (
-                helpers.MockMember(id=77, bot=True, roles=[self.core_dev_role]),
-                *self.get_message_reaction(valid_emoji),
-                helpers.MockMember(id=77, bot=True, roles=[self.core_dev_role]),
-                "reactor is a bot",
-            ),
-            (
-                helpers.MockMember(id=77),
-                helpers.MockMessage(id=95),
-                helpers.MockReaction(emoji=valid_emoji, message=helpers.MockMessage(id=26)),
-                helpers.MockMember(id=77),
-                "messages are not identical",
-            ),
-            (
-                helpers.MockMember(id=77),
-                *self.get_message_reaction("InVaLiD"),
-                helpers.MockMember(id=77),
-                "emoji is invalid",
-            ),
-        )
-
-        for *args, msg in subtests:
-            kwargs = dict(zip(("author", "message", "reaction", "user"), args))
-            with self.subTest(**kwargs, msg=msg):
-                ret_val = self.syncer._reaction_check(*args)
-                self.assertFalse(ret_val)
-
-    async def test_wait_for_confirmation(self):
-        """The message should always be edited and only return True if the emoji is a check mark."""
-        subtests = (
-            (constants.Emojis.check_mark, True, None),
-            ("InVaLiD", False, None),
-            (None, False, asyncio.TimeoutError),
-        )
-
-        for emoji, ret_val, side_effect in subtests:
-            for bot in (True, False):
-                with self.subTest(emoji=emoji, ret_val=ret_val, side_effect=side_effect, bot=bot):
-                    # Set up mocks
-                    message = helpers.MockMessage()
-                    member = helpers.MockMember(bot=bot)
-
-                    self.bot.wait_for.reset_mock()
-                    self.bot.wait_for.return_value = (helpers.MockReaction(emoji=emoji), None)
-                    self.bot.wait_for.side_effect = side_effect
-
-                    # Call the function
-                    actual_return = await self.syncer._wait_for_confirmation(member, message)
-
-                    # Perform assertions
-                    self.bot.wait_for.assert_called_once()
-                    self.assertIn("reaction_add", self.bot.wait_for.call_args[0])
-
-                    message.edit.assert_called_once()
-                    kwargs = message.edit.call_args[1]
-                    self.assertIn("content", kwargs)
-
-                    # Core devs should only be mentioned if the author is a bot.
-                    if bot:
-                        self.assertIn(self.syncer._CORE_DEV_MENTION, kwargs["content"])
-                    else:
-                        self.assertNotIn(self.syncer._CORE_DEV_MENTION, kwargs["content"])
-
-                    self.assertIs(actual_return, ret_val)
-
-
 class SyncerSyncTests(unittest.IsolatedAsyncioTestCase):
     """Tests for main function orchestrating the sync."""
 
     def setUp(self):
         self.bot = helpers.MockBot(user=helpers.MockMember(bot=True))
         self.syncer = TestSyncer(self.bot)
+        self.guild = helpers.MockGuild()
 
-    async def test_sync_respects_confirmation_result(self):
-        """The sync should abort if confirmation fails and continue if confirmed."""
-        mock_message = helpers.MockMessage()
-        subtests = (
-            (True, mock_message),
-            (False, None),
-        )
-
-        for confirmed, message in subtests:
-            with self.subTest(confirmed=confirmed):
-                self.syncer._sync.reset_mock()
-                self.syncer._get_diff.reset_mock()
-
-                diff = _Diff({1, 2, 3}, {4, 5}, None)
-                self.syncer._get_diff.return_value = diff
-                self.syncer._get_confirmation_result = mock.AsyncMock(
-                    return_value=(confirmed, message)
-                )
-
-                guild = helpers.MockGuild()
-                await self.syncer.sync(guild)
-
-                self.syncer._get_diff.assert_called_once_with(guild)
-                self.syncer._get_confirmation_result.assert_called_once()
-
-                if confirmed:
-                    self.syncer._sync.assert_called_once_with(diff)
-                else:
-                    self.syncer._sync.assert_not_called()
-
-    async def test_sync_diff_size(self):
-        """The diff size should be correctly calculated."""
-        subtests = (
-            (6, _Diff({1, 2}, {3, 4}, {5, 6})),
-            (5, _Diff({1, 2, 3}, None, {4, 5})),
-            (0, _Diff(None, None, None)),
-            (0, _Diff(set(), set(), set())),
-        )
-
-        for size, diff in subtests:
-            with self.subTest(size=size, diff=diff):
-                self.syncer._get_diff.reset_mock()
-                self.syncer._get_diff.return_value = diff
-                self.syncer._get_confirmation_result = mock.AsyncMock(return_value=(False, None))
-
-                guild = helpers.MockGuild()
-                await self.syncer.sync(guild)
-
-                self.syncer._get_diff.assert_called_once_with(guild)
-                self.syncer._get_confirmation_result.assert_called_once()
-                self.assertEqual(self.syncer._get_confirmation_result.call_args[0][0], size)
+        # Make sure `_get_diff` returns a MagicMock, not an AsyncMock
+        self.syncer._get_diff.return_value = mock.MagicMock()
 
     async def test_sync_message_edited(self):
         """The message should be edited if one was sent, even if the sync has an API error."""
@@ -316,89 +49,25 @@ async def test_sync_message_edited(self):
         for message, side_effect, should_edit in subtests:
             with self.subTest(message=message, side_effect=side_effect, should_edit=should_edit):
                 self.syncer._sync.side_effect = side_effect
-                self.syncer._get_confirmation_result = mock.AsyncMock(
-                    return_value=(True, message)
-                )
+                ctx = helpers.MockContext()
+                ctx.send.return_value = message
 
-                guild = helpers.MockGuild()
-                await self.syncer.sync(guild)
+                await self.syncer.sync(self.guild, ctx)
 
                 if should_edit:
                     message.edit.assert_called_once()
                     self.assertIn("content", message.edit.call_args[1])
 
-    async def test_sync_confirmation_context_redirect(self):
-        """If ctx is given, a new message should be sent and author should be ctx's author."""
-        mock_member = helpers.MockMember()
+    async def test_sync_message_sent(self):
+        """If ctx is given, a new message should be sent."""
         subtests = (
-            (None, self.bot.user, None),
-            (helpers.MockContext(author=mock_member), mock_member, helpers.MockMessage()),
+            (None, None),
+            (helpers.MockContext(), helpers.MockMessage()),
         )
 
-        for ctx, author, message in subtests:
-            with self.subTest(ctx=ctx, author=author, message=message):
-                if ctx is not None:
-                    ctx.send.return_value = message
-
-                # Make sure `_get_diff` returns a MagicMock, not an AsyncMock
-                self.syncer._get_diff.return_value = mock.MagicMock()
-
-                self.syncer._get_confirmation_result = mock.AsyncMock(return_value=(False, None))
-
-                guild = helpers.MockGuild()
-                await self.syncer.sync(guild, ctx)
+        for ctx, message in subtests:
+            with self.subTest(ctx=ctx, message=message):
+                await self.syncer.sync(self.guild, ctx)
 
                 if ctx is not None:
                     ctx.send.assert_called_once()
-
-                self.syncer._get_confirmation_result.assert_called_once()
-                self.assertEqual(self.syncer._get_confirmation_result.call_args[0][1], author)
-                self.assertEqual(self.syncer._get_confirmation_result.call_args[0][2], message)
-
-    @mock.patch.object(constants.Sync, "max_diff", new=3)
-    async def test_confirmation_result_small_diff(self):
-        """Should always return True and the given message if the diff size is too small."""
-        author = helpers.MockMember()
-        expected_message = helpers.MockMessage()
-
-        for size in (3, 2):  # pragma: no cover
-            with self.subTest(size=size):
-                self.syncer._send_prompt = mock.AsyncMock()
-                self.syncer._wait_for_confirmation = mock.AsyncMock()
-
-                coro = self.syncer._get_confirmation_result(size, author, expected_message)
-                result, actual_message = await coro
-
-                self.assertTrue(result)
-                self.assertEqual(actual_message, expected_message)
-                self.syncer._send_prompt.assert_not_called()
-                self.syncer._wait_for_confirmation.assert_not_called()
-
-    @mock.patch.object(constants.Sync, "max_diff", new=3)
-    async def test_confirmation_result_large_diff(self):
-        """Should return True if confirmed and False if _send_prompt fails or aborted."""
-        author = helpers.MockMember()
-        mock_message = helpers.MockMessage()
-
-        subtests = (
-            (True, mock_message, True, "confirmed"),
-            (False, None, False, "_send_prompt failed"),
-            (False, mock_message, False, "aborted"),
-        )
-
-        for expected_result, expected_message, confirmed, msg in subtests:  # pragma: no cover
-            with self.subTest(msg=msg):
-                self.syncer._send_prompt = mock.AsyncMock(return_value=expected_message)
-                self.syncer._wait_for_confirmation = mock.AsyncMock(return_value=confirmed)
-
-                coro = self.syncer._get_confirmation_result(4, author)
-                actual_result, actual_message = await coro
-
-                self.syncer._send_prompt.assert_called_once_with(None)  # message defaults to None
-                self.assertIs(actual_result, expected_result)
-                self.assertEqual(actual_message, expected_message)
-
-                if expected_message:
-                    self.syncer._wait_for_confirmation.assert_called_once_with(
-                        author, expected_message
-                    )