wikimedia/pywikibot

View on GitHub
tests/logentries_tests.py

Summary

Maintainability
A
35 mins
Test Coverage
#!/usr/bin/env python3
"""Test :mod:`logentries` module."""
#
# (C) Pywikibot team, 2015-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations

import datetime
import unittest
from contextlib import suppress

import pywikibot
from pywikibot.exceptions import HiddenKeyError, NoMoveTargetError
from pywikibot.family import AutoFamily
from pywikibot.logentries import (
    LogEntryFactory,
    OtherLogEntry,
    UserTargetLogEntry,
)
from tests import unittest_print
from tests.aspects import MetaTestCaseClass, TestCase
from tests.utils import skipping


class TestLogentriesBase(TestCase):

    """Base class for log entry tests.

    It uses the German Wikipedia for a current representation of the log
    entries and the test Wikipedia for the future representation. It
    also tests on an older wiki to check that the module works with it.
    It currently uses lobbypedia which as of this commit uses 1.31.6.
    """

    sites = {
        'tewp': {
            'family': 'wikipedia',
            'code': 'test',
            'target': 'Main Page on wheels',
        },
        'dewp': {
            'family': 'wikipedia',
            'code': 'de',
            'target': 'Hauptseite',
        },
        'enwow': {
            'family': 'wowwiki',
            'code': 'en',
            'target': None,
        },
        'old': {
            'family': AutoFamily('lobbypedia',
                                 'https://lobbypedia.de/wiki/Hauptseite'),
            'code': 'de',
            'target': None,
        }
    }

    def _get_logentry(self, logtype):
        """Retrieve a single log entry."""
        if self.site_key == 'old':
            # This is an assertion as the tests don't make sense with newer
            # MW versions and otherwise it might not be visible that the test
            # isn't run on an older wiki.
            self.assertEqual(self.site.mw_version, '1.31.6')

        with skipping(StopIteration,
                      msg=f'No entry found for {logtype!r}'):
            le = next(self.site.logevents(logtype=logtype, total=1))
        return le

    def _test_logevent(self, logtype):
        """Test a single logtype entry."""
        logentry = self._get_logentry(logtype)
        self.assertIn(logtype, logentry.__class__.__name__.lower())
        self.assertEqual(logentry._expected_type, logtype)

        if logtype not in LogEntryFactory._logtypes:
            self.assertIsInstance(logentry, OtherLogEntry)

        # check that we only have the new implementation
        self.assertNotIn(logentry.type(), logentry.data)
        self.assertIsInstance(logentry.action(), str)

        try:
            self.assertIsInstance(logentry.comment(), str)
            self.assertIsInstance(logentry.user(), str)
            self.assertEqual(logentry.user(), logentry['user'])
        except HiddenKeyError as e:  # pragma: no cover
            self.assertRegex(
                str(e),
                r"Log entry \([^)]+\) has a hidden '\w+' key and you "
                r"don't have permission to view it")
        except KeyError as e:  # pragma: no cover
            self.assertRegex(str(e), "Log entry ([^)]+) has no 'comment' key")
        else:
            self.assertEqual(logentry.comment(), logentry['comment'])

        self.assertIsInstance(logentry.logid(), int)
        self.assertIsInstance(logentry.timestamp(), pywikibot.Timestamp)

        if 'title' in logentry.data:  # title may be missing
            self.assertIsInstance(logentry.ns(), int)
            self.assertIsInstance(logentry.pageid(), int)

            # test new UserDict style
            self.assertEqual(logentry.data['title'], logentry['title'])
            self.assertEqual(logentry.ns(), logentry['ns'])
            self.assertEqual(logentry.pageid(), logentry['pageid'])

            self.assertGreaterEqual(logentry.ns(), -2)
            self.assertGreaterEqual(logentry.pageid(), 0)
            if logtype == 'block' and logentry.isAutoblockRemoval:
                self.assertIsInstance(logentry.page(), int)
            elif isinstance(logentry, UserTargetLogEntry):
                self.assertIsInstance(logentry.page(), pywikibot.User)
            elif logtype == 'upload':
                self.assertIsInstance(logentry.page(), pywikibot.FilePage)
            else:
                self.assertIsInstance(logentry.page(), pywikibot.Page)
        else:  # pragma: no cover
            with self.assertRaises(KeyError):
                logentry.page()

        self.assertEqual(logentry.type(), logtype)
        self.assertGreaterEqual(logentry.logid(), 0)

        # test new UserDict style
        self.assertEqual(logentry.type(), logentry['type'])
        self.assertEqual(logentry.logid(), logentry['logid'])


class LogentriesTestMeta(MetaTestCaseClass):

    """Test meta class for TestLogentries."""

    def __new__(cls, name, bases, dct):
        """Create the new class."""
        def test_method(logtype):
            def test_logevent(self, key):
                """Test a single logtype entry."""
                site = self.sites[key]['site']
                if logtype not in site.logtypes:
                    self.skipTest(
                        f'{key}: {logtype!r} logtype not available on {site}.')
                if logtype == 'upload' and key == 'old':
                    self.skipTest(f'{key}: frequently timeouts for '
                                  f'{logtype!r} logtype on {site} (T334729).')

                self._test_logevent(logtype)

            return test_logevent

        # create test methods for the support logtype classes
        for logtype in LogEntryFactory._logtypes:
            cls.add_method(dct, f'test_{logtype.title()}Entry',
                           test_method(logtype))

        return super().__new__(cls, name, bases, dct)


class TestLogentries(TestLogentriesBase, metaclass=LogentriesTestMeta):

    """Test general LogEntry properties."""


class TestSimpleLogentries(TestLogentriesBase):

    """Test logentry classes without special classes."""

    def test_simple_entries(self, key):
        """Test those entries which don't have an extra LogEntry subclass."""
        # Unfortunately it's not possible to use the metaclass to create a
        # bunch of test methods for this too as the site instances haven't
        # been initialized yet.
        for simple_type in (self.site.logtypes
                            - set(LogEntryFactory._logtypes)):
            if not simple_type:
                # paraminfo also reports an empty string as a type
                continue  # pragma: no cover
            try:
                self._test_logevent(simple_type)
            except StopIteration:  # pragma: no cover
                unittest_print(
                    f'Unable to test "{simple_type}" on "{key}" because there'
                    ' are no log entries with that type.')


class TestLogentryParams(TestLogentriesBase):

    """Test LogEntry properties specific to their action."""

    def test_block_entry(self, key):
        """Test BlockEntry methods."""
        # only 'block' entries can be tested
        for logentry in self.site.logevents(logtype='block', total=5):
            if logentry.action() == 'block':
                self.assertIsInstance(logentry.flags(), list)
                # Check that there are no empty strings
                for flag in logentry.flags():
                    self.assertIsInstance(flag, str)
                    self.assertNotEqual(flag, '')
                if logentry.expiry() is not None:
                    self.assertIsInstance(logentry.expiry(),
                                          pywikibot.Timestamp)
                    self.assertIsInstance(logentry.duration(),
                                          datetime.timedelta)
                    self.assertEqual(
                        logentry.timestamp() + logentry.duration(),
                        logentry.expiry())
                else:
                    self.assertIsNone(logentry.duration())
                break

    def test_rights_entry(self, key):
        """Test RightsEntry methods."""
        logentry = self._get_logentry('rights')
        self.assertIsInstance(logentry.oldgroups, list)
        self.assertIsInstance(logentry.newgroups, list)

    def test_move_entry(self, key):
        """Test MoveEntry methods."""
        logentry = self._get_logentry('move')
        if 'actionhidden' in logentry:
            self.skipTest(
                f'move action was hidden due to {logentry.comment()}')
        self.assertIsInstance(logentry.target_ns, pywikibot.site.Namespace)
        self.assertEqual(logentry.target_page.namespace(),
                         logentry.target_ns.id)
        self.assertIsInstance(logentry.target_title, str)
        self.assertIsInstance(logentry.target_page, pywikibot.Page)
        self.assertIsInstance(logentry.suppressedredirect(), bool)

    def test_patrol_entry(self, key):
        """Test PatrolEntry methods."""
        logentry = self._get_logentry('patrol')
        self.assertIsInstance(logentry.current_id, int)
        self.assertIsInstance(logentry.previous_id, int)
        self.assertIsInstance(logentry.auto, bool)

    def test_moved_target(self, key):
        """Test moved_target method."""
        # main page was moved around
        mainpage = self.get_mainpage(self.site)
        if self.sites[key]['target'] is None:
            self.skipTest('No moved target')
        target = mainpage.moved_target()
        self.assertIsInstance(target, pywikibot.Page)
        self.assertEqual(target.title(),
                         self.sites[key]['target'])
        # main page was moved back again, we test it.
        self.assertEqual(mainpage, target.moved_target())

    def test_moved_target_fail_old(self):
        """Test moved_target method failing on older wiki."""
        site = self.get_site('old')
        with self.assertRaises(NoMoveTargetError):
            self.get_mainpage(site).moved_target()

    def test_moved_target_fail_de(self):
        """Test moved_target method failing on de-wiki."""
        page = pywikibot.Page(self.get_site('dewp'), 'Main Page')
        with self.assertRaises(NoMoveTargetError):
            page.moved_target()

    def test_thanks_page(self, key):
        """Test Thanks page method return type."""
        if not self.site.has_extension('Thanks'):
            self.skipTest('Thanks extension not available.')
        logentry = self._get_logentry('thanks')
        self.assertIsInstance(logentry.page(), pywikibot.User)

    def test_equality(self):
        """Test equality of LogEntry instances."""
        site = self.get_site('dewp')
        other_site = self.get_site('tewp')
        gen1 = site.logevents(reverse=True, total=2)
        gen2 = site.logevents(reverse=True, total=2)
        le1 = next(gen1)
        le2 = next(gen2)
        le3 = next(other_site.logevents(reverse=True, total=1))
        le4 = next(gen1)
        le5 = next(gen2)
        self.assertEqual(le1, le2)
        self.assertFalse(le1 != le2)  # noqa: H204
        self.assertNotEqual(le1, le3)
        self.assertNotEqual(le1, site)
        self.assertIsInstance(le4, OtherLogEntry)
        self.assertIsInstance(le5, OtherLogEntry)
        self.assertEqual(type(le4), type(le5))


if __name__ == '__main__':
    with suppress(SystemExit):
        unittest.main()