tests/logentries_tests.py
#!/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()