class TestRemoveTranslations(TestCase):
    """Test `remove_translations`."""
    layer = LaunchpadZopelessLayer

    def setUp(self):
        super(TestRemoveTranslations, self).setUp()
        # Acquire privileges to delete TranslationMessages.  That's not
        # something we normally do.  Actually we should test under
        # rosettaadmin, but that user does not have all the privileges
        # needed to set up this test.  A separate doctest
        # remove-translations-by.txt tests a realistic run of the
        # remove-translations-by.py script under the actual rosettaadmin
        # db user.
        switch_dbuser('postgres')

        # Set up a template with Dutch and German translations.  The
        # messages we set up here are invariant; they remain untouched
        # by deletions done in the test case.
        self.factory = LaunchpadObjectFactory()
        self.nl_pofile = self.factory.makePOFile('nl')
        self.potemplate = self.nl_pofile.potemplate
        self.de_pofile = self.factory.makePOFile('de',
                                                 potemplate=self.potemplate)

        self.nl_message, self.de_message = self._makeMessages(
            "This message is not to be deleted.",
            "Dit bericht mag niet worden verwijderd.",
            "Diese Nachricht soll nicht erloescht werden.")

        self.untranslated_message = self.factory.makePOTMsgSet(
            self.potemplate, 'This message is untranslated.', sequence=0)

        self._checkInvariant()

    def _setTranslation(self,
                        potmsgset,
                        pofile,
                        text,
                        submitter=None,
                        is_current_upstream=False):
        """Set translation for potmsgset in pofile to text."""
        if submitter is None:
            submitter = self.potemplate.owner
        return self.factory.makeCurrentTranslationMessage(
            pofile,
            potmsgset,
            translator=submitter,
            translations={0: text},
            current_other=is_current_upstream)

    def _makeMessages(self,
                      template_text,
                      nl_text,
                      de_text,
                      submitter=None,
                      is_current_upstream=False):
        """Create message, and translate it to Dutch & German."""
        message = self.factory.makePOTMsgSet(self.potemplate,
                                             template_text,
                                             sequence=0)
        new_nl_message = self._setTranslation(
            message,
            self.nl_pofile,
            nl_text,
            submitter=submitter,
            is_current_upstream=is_current_upstream)
        new_de_message = self._setTranslation(
            message,
            self.de_pofile,
            de_text,
            submitter=submitter,
            is_current_upstream=is_current_upstream)
        return new_nl_message, new_de_message

    def _getContents(self, pofile):
        return sorted(message.msgstr0.translation
                      for message in pofile.translation_messages
                      if message.msgstr0 is not None)

    def _checkInvariant(self):
        """Check that our translations are in their original state.

        Tests in this test case don't work in the usual way, by making
        changes and then testing for them.  Instead they make changes by
        creating new messages, and then using `remove_translations` to
        undo those changes.

        We see that a removal worked correctly by verifying that the
        invariant is restored.
        """
        # First make sure we're not reading out of cache.
        Store.of(self.nl_pofile).flush()

        self.assertEqual(self._getContents(self.nl_pofile),
                         ["Dit bericht mag niet worden verwijderd."])
        self.assertEqual(self._getContents(self.de_pofile),
                         ["Diese Nachricht soll nicht erloescht werden."])

    def _removeMessages(self, **kwargs):
        """Front-end for `remove_translations`.  Flushes changes first."""
        Store.of(self.potemplate).flush()
        return remove_translations(**kwargs)

    def test_RemoveNone(self):
        # If no messages match the given constraints, nothing is
        # deleted.
        rowcount = self._removeMessages(submitter=1,
                                        ids=[self.de_message.id],
                                        language_code='br')
        self.assertEqual(rowcount, 0)
        self._checkInvariant()

    def test_RemoveById(self):
        # We can remove messages by id.  Other messages are not
        # affected.
        new_nl_message1 = self._setTranslation(self.untranslated_message,
                                               self.nl_pofile,
                                               "A Dutch translation")
        new_nl_message2 = self._setTranslation(self.untranslated_message,
                                               self.nl_pofile, "Double Dutch")
        self.assertEqual(self._getContents(self.nl_pofile), [
            "A Dutch translation",
            "Dit bericht mag niet worden verwijderd.",
            "Double Dutch",
        ])

        rowcount = self._removeMessages(
            ids=[new_nl_message1.id, new_nl_message2.id])

        self.assertEqual(rowcount, 2)
        self._checkInvariant()

    def test_RemoveBySubmitter(self):
        # Remove messages by submitter id.
        carlos = getUtility(IPersonSet).getByName('carlos')
        (new_nl_message,
         new_de_message) = self._makeMessages("Submitted by Carlos",
                                              "Ingevoerd door Carlos",
                                              "Von Carlos eingefuehrt",
                                              submitter=carlos)

        # Ensure that at least one message's reviewer is not the same
        # as the submitter, so we know we're not accidentally matching
        # on reviewer instead.
        new_nl_message.reviewer = self.potemplate.owner

        self._removeMessages(submitter=carlos)
        self._checkInvariant()

    def test_RemoveByReviewer(self):
        # Remove messages by reviewer id.
        carlos = getUtility(IPersonSet).getByName('carlos')
        (new_nl_message,
         new_de_message) = self._makeMessages("Submitted by Carlos",
                                              "Ingevoerd door Carlos",
                                              "Von Carlos eingefuehrt")
        new_nl_message.reviewer = carlos
        new_de_message.reviewer = carlos

        self._removeMessages(reviewer=carlos)
        self._checkInvariant()

    def test_RemoveByTemplate(self):
        # Remove messages by template.  Limit this deletion by ids as
        # well to avoid breaking the test invariant.  To show that the
        # template limitation really does add a limit on top of the ids
        # themselves, we also pass the id of another message in a
        # different template.  That message is not deleted.
        (new_nl_message,
         new_de_message) = self._makeMessages("Foo", "Foe", "Fu")

        unrelated_nl_pofile = self.factory.makePOFile('nl')
        potmsgset = self.factory.makePOTMsgSet(unrelated_nl_pofile.potemplate,
                                               'Foo',
                                               sequence=0)
        unrelated_nl_message = self.factory.makeCurrentTranslationMessage(
            unrelated_nl_pofile,
            potmsgset,
            translator=unrelated_nl_pofile.potemplate.owner,
            translations={0: "Foe"})

        ids = [new_nl_message.id, new_de_message.id, unrelated_nl_message.id]
        self._removeMessages(ids=ids, potemplate=self.potemplate.id)

        self._checkInvariant()
        self.assertEqual(self._getContents(unrelated_nl_pofile), ["Foe"])

    def test_RemoveByLanguage(self):
        # Remove messages by language.  Pass the ids of one Dutch
        # message and one German message, but specify Dutch as the
        # language to delete from; only the Dutch message is deleted.
        potmsgset = self.factory.makePOTMsgSet(self.potemplate,
                                               'Bar',
                                               sequence=0)
        message = self._setTranslation(potmsgset, self.nl_pofile, 'Cafe')

        self._removeMessages(ids=[message.id, self.de_message.id],
                             language_code='nl')

        self._checkInvariant()

    def test_RemoveByNotLanguage(self):
        # Remove messages, but spare otherwise matching messages that
        # are in German.
        potmsgset = self.factory.makePOTMsgSet(self.potemplate,
                                               'Hi',
                                               sequence=0)
        message = self._setTranslation(potmsgset, self.nl_pofile, 'Hoi')

        self._removeMessages(ids=[message.id, self.de_message.id],
                             language_code='de',
                             not_language=True)

        self._checkInvariant()

    def test_RemoveCurrent(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message,
         new_de_message) = self._makeMessages("translate", "vertalen",
                                              "uebersetzen")
        self.nl_message.is_current_upstream = False

        messages_to_delete = [self.nl_message, new_nl_message, new_de_message]
        current_upstream_messages = [
            message for message in messages_to_delete
            if message.is_current_upstream
        ]
        ids = [message.id for message in messages_to_delete]
        logger = logging.getLogger('test_remove_translations')
        logger.setLevel(logging.WARN)
        loghandler = Handler(self)
        loghandler.add(logger.name)
        self._removeMessages(ids=ids, is_current_upstream=True, logger=logger)

        self.nl_message.is_current_upstream = True
        self._checkInvariant()
        loghandler.assertLogsMessage('Deleting messages currently in use:',
                                     level=logging.WARN)
        for message in current_upstream_messages:
            loghandler.assertLogsMessage(
                'Message %i is a current translation in upstream' % message.id,
                level=logging.WARN)
        self.assertEqual(1 + len(current_upstream_messages),
                         len(loghandler.records))

    def test_RemoveNotCurrent(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message,
         new_de_message) = self._makeMessages("write", "schrijven",
                                              "schreiben")
        new_nl_message.is_current_upstream = False
        new_de_message.is_current_upstream = False

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_upstream=False)

        self._checkInvariant()

    def test_RemoveImported(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message,
         new_de_message) = self._makeMessages("book", "boek", "Buch")
        new_nl_message.is_current_ubuntu = True
        new_de_message.is_current_ubuntu = True

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_ubuntu=True)

        self._checkInvariant()

    def test_RemoveNotImported(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message,
         new_de_message) = self._makeMessages("helicopter", "helikopter",
                                              "Hubschauber")
        self.nl_message.is_current_ubuntu = True

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_ubuntu=False)

        self.nl_message.is_current_ubuntu = False
        self._checkInvariant()

    def test_RemoveMsgId(self):
        # Remove translations by msgid_singular.
        (new_nl_message,
         new_de_message) = self._makeMessages("save", "bewaren", "speichern")

        self._removeMessages(msgid_singular="save")

        self._checkInvariant()

    def test_RemoveOrigin(self):
        # Remove translations by origin.
        self.assertEqual(self.nl_message.origin,
                         RosettaTranslationOrigin.ROSETTAWEB)
        (new_nl_message,
         new_de_message) = self._makeMessages("new",
                                              "nieuw",
                                              "neu",
                                              is_current_upstream=True)
        removeSecurityProxy(new_nl_message).origin = (
            RosettaTranslationOrigin.SCM)
        removeSecurityProxy(new_de_message).origin = (
            RosettaTranslationOrigin.SCM)

        self._removeMessages(potemplate=self.potemplate,
                             origin=RosettaTranslationOrigin.SCM)

        self._checkInvariant()

    def test_remove_by_license_rejection(self):
        # Remove translations submitted by users who rejected the
        # licensing agreement.
        refusenik = self.factory.makePerson()

        self._makeMessages("Don't download this song",
                           "Niet delen",
                           "Nicht teilen",
                           submitter=refusenik)

        TranslationRelicensingAgreement(person=refusenik,
                                        allow_relicensing=False)

        self._removeMessages(reject_license=True)

        self._checkInvariant()

    def test_remove_unlicensed_none(self):
        # Removing translations whose submitters rejected our
        # translations licence does not affect translations by those who
        # haven't answered the question yet.
        self._removeMessages(reject_license=True)

        self._checkInvariant()

    def test_remove_unlicensed_when_licensed(self):
        # Removing translations whose submitters rejected our
        # translations licence does not affect translations by those who
        # agreed to license.
        answer = TranslationRelicensingAgreement(
            person=self.nl_message.submitter, allow_relicensing=True)

        try:
            self._removeMessages(reject_license=True)
            self._checkInvariant()
        finally:
            # Clean up.
            answer.destroySelf()

    def test_remove_unlicensed_restriction(self):
        # When removing unlicensed translations, other restrictions
        # still apply.
        self.nl_message.is_current_upstream = True
        self.de_message.is_current_upstream = True
        answer = TranslationRelicensingAgreement(
            person=self.nl_message.submitter, allow_relicensing=False)

        try:
            self._removeMessages(reject_license=True,
                                 is_current_upstream=False)
            self._checkInvariant()
        finally:
            # Clean up.
            answer.destroySelf()
class TestRemoveTranslations(TestCase):
    """Test `remove_translations`."""
    layer = LaunchpadZopelessLayer

    def setUp(self):
        super(TestRemoveTranslations, self).setUp()
        # Acquire privileges to delete TranslationMessages.  That's not
        # something we normally do.  Actually we should test under
        # rosettaadmin, but that user does not have all the privileges
        # needed to set up this test.  A separate doctest
        # remove-translations-by.txt tests a realistic run of the
        # remove-translations-by.py script under the actual rosettaadmin
        # db user.
        switch_dbuser('postgres')

        # Set up a template with Dutch and German translations.  The
        # messages we set up here are invariant; they remain untouched
        # by deletions done in the test case.
        self.factory = LaunchpadObjectFactory()
        self.nl_pofile = self.factory.makePOFile('nl')
        self.potemplate = self.nl_pofile.potemplate
        self.de_pofile = self.factory.makePOFile(
            'de', potemplate=self.potemplate)

        self.nl_message, self.de_message = self._makeMessages(
            "This message is not to be deleted.",
            "Dit bericht mag niet worden verwijderd.",
            "Diese Nachricht soll nicht erloescht werden.")

        self.untranslated_message = self.factory.makePOTMsgSet(
            self.potemplate, 'This message is untranslated.', sequence=0)

        self._checkInvariant()

    def _setTranslation(self, potmsgset, pofile, text, submitter=None,
                        is_current_upstream=False):
        """Set translation for potmsgset in pofile to text."""
        if submitter is None:
            submitter = self.potemplate.owner
        return self.factory.makeCurrentTranslationMessage(
            pofile, potmsgset, translator=submitter,
            translations={0: text}, current_other=is_current_upstream)

    def _makeMessages(self, template_text, nl_text, de_text,
                      submitter=None, is_current_upstream=False):
        """Create message, and translate it to Dutch & German."""
        message = self.factory.makePOTMsgSet(self.potemplate, template_text,
                                             sequence=0)
        new_nl_message = self._setTranslation(
            message, self.nl_pofile, nl_text, submitter=submitter,
            is_current_upstream=is_current_upstream)
        new_de_message = self._setTranslation(
            message, self.de_pofile, de_text, submitter=submitter,
            is_current_upstream=is_current_upstream)
        return new_nl_message, new_de_message

    def _getContents(self, pofile):
        return sorted(
            message.msgstr0.translation
            for message in pofile.translation_messages
            if message.msgstr0 is not None)

    def _checkInvariant(self):
        """Check that our translations are in their original state.

        Tests in this test case don't work in the usual way, by making
        changes and then testing for them.  Instead they make changes by
        creating new messages, and then using `remove_translations` to
        undo those changes.

        We see that a removal worked correctly by verifying that the
        invariant is restored.
        """
        # First make sure we're not reading out of cache.
        Store.of(self.nl_pofile).flush()

        self.assertEqual(
            self._getContents(self.nl_pofile),
            ["Dit bericht mag niet worden verwijderd."])
        self.assertEqual(
            self._getContents(self.de_pofile),
            ["Diese Nachricht soll nicht erloescht werden."])

    def _removeMessages(self, **kwargs):
        """Front-end for `remove_translations`.  Flushes changes first."""
        Store.of(self.potemplate).flush()
        return remove_translations(**kwargs)

    def test_RemoveNone(self):
        # If no messages match the given constraints, nothing is
        # deleted.
        rowcount = self._removeMessages(
            submitter=1, ids=[self.de_message.id], language_code='br')
        self.assertEqual(rowcount, 0)
        self._checkInvariant()

    def test_RemoveById(self):
        # We can remove messages by id.  Other messages are not
        # affected.
        new_nl_message1 = self._setTranslation(
            self.untranslated_message, self.nl_pofile, "A Dutch translation")
        new_nl_message2 = self._setTranslation(
            self.untranslated_message, self.nl_pofile, "Double Dutch")
        self.assertEqual(
            self._getContents(self.nl_pofile), [
                "A Dutch translation",
                "Dit bericht mag niet worden verwijderd.",
                "Double Dutch",
                ])

        rowcount = self._removeMessages(
            ids=[new_nl_message1.id, new_nl_message2.id])

        self.assertEqual(rowcount, 2)
        self._checkInvariant()

    def test_RemoveBySubmitter(self):
        # Remove messages by submitter id.
        carlos = getUtility(IPersonSet).getByName('carlos')
        (new_nl_message, new_de_message) = self._makeMessages(
            "Submitted by Carlos", "Ingevoerd door Carlos",
            "Von Carlos eingefuehrt", submitter=carlos)

        # Ensure that at least one message's reviewer is not the same
        # as the submitter, so we know we're not accidentally matching
        # on reviewer instead.
        new_nl_message.reviewer = self.potemplate.owner

        self._removeMessages(submitter=carlos)
        self._checkInvariant()

    def test_RemoveByReviewer(self):
        # Remove messages by reviewer id.
        carlos = getUtility(IPersonSet).getByName('carlos')
        (new_nl_message, new_de_message) = self._makeMessages(
            "Submitted by Carlos", "Ingevoerd door Carlos",
            "Von Carlos eingefuehrt")
        new_nl_message.reviewer = carlos
        new_de_message.reviewer = carlos

        self._removeMessages(reviewer=carlos)
        self._checkInvariant()

    def test_RemoveByTemplate(self):
        # Remove messages by template.  Limit this deletion by ids as
        # well to avoid breaking the test invariant.  To show that the
        # template limitation really does add a limit on top of the ids
        # themselves, we also pass the id of another message in a
        # different template.  That message is not deleted.
        (new_nl_message, new_de_message) = self._makeMessages(
            "Foo", "Foe", "Fu")

        unrelated_nl_pofile = self.factory.makePOFile('nl')
        potmsgset = self.factory.makePOTMsgSet(
            unrelated_nl_pofile.potemplate, 'Foo',
            sequence=0)
        unrelated_nl_message = self.factory.makeCurrentTranslationMessage(
            unrelated_nl_pofile, potmsgset,
            translator=unrelated_nl_pofile.potemplate.owner,
            translations={0: "Foe"})

        ids = [new_nl_message.id, new_de_message.id, unrelated_nl_message.id]
        self._removeMessages(
            ids=ids, potemplate=self.potemplate.id)

        self._checkInvariant()
        self.assertEqual(self._getContents(unrelated_nl_pofile), ["Foe"])

    def test_RemoveByLanguage(self):
        # Remove messages by language.  Pass the ids of one Dutch
        # message and one German message, but specify Dutch as the
        # language to delete from; only the Dutch message is deleted.
        potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Bar',
                                               sequence=0)
        message = self._setTranslation(potmsgset, self.nl_pofile, 'Cafe')

        self._removeMessages(
            ids=[message.id, self.de_message.id], language_code='nl')

        self._checkInvariant()

    def test_RemoveByNotLanguage(self):
        # Remove messages, but spare otherwise matching messages that
        # are in German.
        potmsgset = self.factory.makePOTMsgSet(self.potemplate, 'Hi',
                                               sequence=0)
        message = self._setTranslation(potmsgset, self.nl_pofile, 'Hoi')

        self._removeMessages(
            ids=[message.id, self.de_message.id], language_code='de',
            not_language=True)

        self._checkInvariant()

    def test_RemoveCurrent(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message, new_de_message) = self._makeMessages(
            "translate", "vertalen", "uebersetzen")
        self.nl_message.is_current_upstream = False

        messages_to_delete = [self.nl_message, new_nl_message, new_de_message]
        current_upstream_messages = [
            message for message in messages_to_delete
            if message.is_current_upstream]
        ids = [message.id for message in messages_to_delete]
        logger = logging.getLogger('test_remove_translations')
        logger.setLevel(logging.WARN)
        loghandler = Handler(self)
        loghandler.add(logger.name)
        self._removeMessages(ids=ids, is_current_upstream=True, logger=logger)

        self.nl_message.is_current_upstream = True
        self._checkInvariant()
        loghandler.assertLogsMessage(
            'Deleting messages currently in use:', level=logging.WARN)
        for message in current_upstream_messages:
            loghandler.assertLogsMessage(
                'Message %i is a current translation in upstream'
                % message.id,
                level=logging.WARN)
        self.assertEqual(
            1 + len(current_upstream_messages), len(loghandler.records))

    def test_RemoveNotCurrent(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message, new_de_message) = self._makeMessages(
            "write", "schrijven", "schreiben")
        new_nl_message.is_current_upstream = False
        new_de_message.is_current_upstream = False

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_upstream=False)

        self._checkInvariant()

    def test_RemoveImported(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message, new_de_message) = self._makeMessages(
            "book", "boek", "Buch")
        new_nl_message.is_current_ubuntu = True
        new_de_message.is_current_ubuntu = True

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_ubuntu=True)

        self._checkInvariant()

    def test_RemoveNotImported(self):
        # Remove current messages, but not non-current messages.
        (new_nl_message, new_de_message) = self._makeMessages(
            "helicopter", "helikopter", "Hubschauber")
        self.nl_message.is_current_ubuntu = True

        ids = [self.nl_message.id, new_nl_message.id, new_de_message.id]
        self._removeMessages(ids=ids, is_current_ubuntu=False)

        self.nl_message.is_current_ubuntu = False
        self._checkInvariant()

    def test_RemoveMsgId(self):
        # Remove translations by msgid_singular.
        (new_nl_message, new_de_message) = self._makeMessages(
            "save", "bewaren", "speichern")

        self._removeMessages(msgid_singular="save")

        self._checkInvariant()

    def test_RemoveOrigin(self):
        # Remove translations by origin.
        self.assertEqual(
            self.nl_message.origin, RosettaTranslationOrigin.ROSETTAWEB)
        (new_nl_message, new_de_message) = self._makeMessages(
            "new", "nieuw", "neu", is_current_upstream=True)
        removeSecurityProxy(new_nl_message).origin = (
            RosettaTranslationOrigin.SCM)
        removeSecurityProxy(new_de_message).origin = (
            RosettaTranslationOrigin.SCM)

        self._removeMessages(
            potemplate=self.potemplate, origin=RosettaTranslationOrigin.SCM)

        self._checkInvariant()

    def test_remove_by_license_rejection(self):
        # Remove translations submitted by users who rejected the
        # licensing agreement.
        refusenik = self.factory.makePerson()

        self._makeMessages(
            "Don't download this song", "Niet delen", "Nicht teilen",
            submitter=refusenik)

        TranslationRelicensingAgreement(
            person=refusenik, allow_relicensing=False)

        self._removeMessages(reject_license=True)

        self._checkInvariant()

    def test_remove_unlicensed_none(self):
        # Removing translations whose submitters rejected our
        # translations licence does not affect translations by those who
        # haven't answered the question yet.
        self._removeMessages(reject_license=True)

        self._checkInvariant()

    def test_remove_unlicensed_when_licensed(self):
        # Removing translations whose submitters rejected our
        # translations licence does not affect translations by those who
        # agreed to license.
        answer = TranslationRelicensingAgreement(
            person=self.nl_message.submitter, allow_relicensing=True)

        try:
            self._removeMessages(reject_license=True)
            self._checkInvariant()
        finally:
            # Clean up.
            answer.destroySelf()

    def test_remove_unlicensed_restriction(self):
        # When removing unlicensed translations, other restrictions
        # still apply.
        self.nl_message.is_current_upstream = True
        self.de_message.is_current_upstream = True
        answer = TranslationRelicensingAgreement(
            person=self.nl_message.submitter, allow_relicensing=False)

        try:
            self._removeMessages(
                reject_license=True, is_current_upstream=False)
            self._checkInvariant()
        finally:
            # Clean up.
            answer.destroySelf()