wikimedia/pywikibot

View on GitHub
tests/wbtypes_tests.py

Summary

Maintainability
C
1 day
Test Coverage
#!/usr/bin/env python3
"""Tests for the Wikidata parts of the page module."""
#
# (C) Pywikibot team, 2008-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations

import datetime
import operator
import unittest
from contextlib import suppress
from decimal import Decimal

import pywikibot
from pywikibot.page import ItemPage, Page
from tests.aspects import WikidataTestCase


class WbRepresentationTestCase(WikidataTestCase):

    """Test methods inherited or extended from _WbRepresentation."""

    def _test_hashable(self, representation):
        """Test that the representation is hashable."""
        list_of_dupes = [representation, representation]
        self.assertLength(set(list_of_dupes), 1)


class TestWikibaseCoordinate(WbRepresentationTestCase):

    """Test Wikibase Coordinate data type."""

    dry = True

    def test_Coordinate_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        repo = self.get_repo()
        coord = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe='moon')
        self._test_hashable(coord)

    def test_Coordinate_dim(self):
        """Test Coordinate dimension."""
        repo = self.get_repo()
        x = pywikibot.Coordinate(site=repo, lat=12.0, lon=13.0, precision=5.0)
        self.assertEqual(x.precisionToDim(), 544434)
        self.assertIsInstance(x.precisionToDim(), int)
        y = pywikibot.Coordinate(site=repo, lat=12.0, lon=13.0, dim=54444)
        self.assertEqual(y.precision, 0.500005084017101)
        self.assertIsInstance(y.precision, float)
        z = pywikibot.Coordinate(site=repo, lat=12.0, lon=13.0)
        regex = r'^No values set for dim or precision$'
        with self.assertRaisesRegex(ValueError, regex):
            z.precisionToDim()

    def test_Coordinate_plain_globe(self):
        """Test setting Coordinate globe from a plain-text value."""
        repo = self.get_repo()
        coord = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe='moon')
        self.assertEqual(coord.toWikibase(),
                         {'latitude': 12.0, 'longitude': 13.0,
                          'altitude': None, 'precision': 0,
                          'globe': 'http://www.wikidata.org/entity/Q405'})

    def test_Coordinate_entity_uri_globe(self):
        """Test setting Coordinate globe from an entity uri."""
        repo = self.get_repo()
        coord = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe_item='http://www.wikidata.org/entity/Q123')
        self.assertEqual(coord.toWikibase(),
                         {'latitude': 12.0, 'longitude': 13.0,
                          'altitude': None, 'precision': 0,
                          'globe': 'http://www.wikidata.org/entity/Q123'})


class TestWikibaseCoordinateNonDry(WbRepresentationTestCase):

    """Test Wikibase Coordinate data type (non-dry).

    These can be moved to TestWikibaseCoordinate once DrySite has been bumped
    to the appropriate version.
    """

    def test_Coordinate_item_globe(self):
        """Test setting Coordinate globe from an ItemPage."""
        repo = self.get_repo()
        coord = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe_item=ItemPage(repo, 'Q123'))
        self.assertEqual(coord.toWikibase(),
                         {'latitude': 12.0, 'longitude': 13.0,
                          'altitude': None, 'precision': 0,
                          'globe': 'http://www.wikidata.org/entity/Q123'})

    def test_Coordinate_get_globe_item_from_uri(self):
        """Test getting globe item from Coordinate with entity uri globe."""
        repo = self.get_repo()
        q = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe_item='http://www.wikidata.org/entity/Q123')
        self.assertEqual(q.get_globe_item(), ItemPage(repo, 'Q123'))

    def test_Coordinate_get_globe_item_from_itempage(self):
        """Test getting globe item from Coordinate with ItemPage globe."""
        repo = self.get_repo()
        globe = ItemPage(repo, 'Q123')
        q = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0, globe_item=globe)
        self.assertEqual(q.get_globe_item(), ItemPage(repo, 'Q123'))

    def test_Coordinate_get_globe_item_from_plain_globe(self):
        """Test getting globe item from Coordinate with plain text globe."""
        repo = self.get_repo()
        q = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0, globe='moon')
        self.assertEqual(q.get_globe_item(), ItemPage(repo, 'Q405'))

    def test_Coordinate_get_globe_item_provide_repo(self):
        """Test getting globe item from Coordinate, providing repo."""
        repo = self.get_repo()
        q = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe_item='http://www.wikidata.org/entity/Q123')
        self.assertEqual(q.get_globe_item(repo), ItemPage(repo, 'Q123'))

    def test_Coordinate_get_globe_item_different_repo(self):
        """Test getting globe item in different repo from Coordinate."""
        repo = self.get_repo()
        test_repo = pywikibot.Site('test', 'wikidata')
        q = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0,
            globe_item='http://test.wikidata.org/entity/Q123')
        self.assertEqual(q.get_globe_item(test_repo),
                         ItemPage(test_repo, 'Q123'))

    def test_Coordinate_equality(self):
        """Test Coordinate equality with different globe representations."""
        repo = self.get_repo()
        a = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0.1,
            globe='moon')
        b = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0.1,
            globe_item='http://www.wikidata.org/entity/Q405')
        c = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0.1,
            globe_item=ItemPage(repo, 'Q405'))
        d = pywikibot.Coordinate(
            site=repo, lat=12.0, lon=13.0, precision=0.1,
            globe_item='http://test.wikidata.org/entity/Q405')
        self.assertEqual(a, b)
        self.assertEqual(b, c)
        self.assertEqual(c, a)
        self.assertNotEqual(a, d)
        self.assertNotEqual(b, d)
        self.assertNotEqual(c, d)


class TestWbTime(WbRepresentationTestCase):

    """Test Wikibase WbTime data type."""

    dry = True

    def test_WbTime_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        repo = self.get_repo()
        t = pywikibot.WbTime(site=repo, year=2010, month=0, day=0, hour=12,
                             minute=43)
        self._test_hashable(t)

    def test_WbTime_timestr(self):
        """Test timestr functions of WbTime."""
        repo = self.get_repo()
        t = pywikibot.WbTime(site=repo, year=2010, month=0, day=0, hour=12,
                             minute=43)
        self.assertEqual(t.toTimestr(), '+00000002010-00-00T12:43:00Z')
        self.assertEqual(t.toTimestr(force_iso=True), '+2010-01-01T12:43:00Z')

        t = pywikibot.WbTime(site=repo, year=2010, hour=12, minute=43)
        self.assertEqual(t.toTimestr(), '+00000002010-01-01T12:43:00Z')
        self.assertEqual(t.toTimestr(force_iso=True), '+2010-01-01T12:43:00Z')

        t = pywikibot.WbTime(site=repo, year=-2010, hour=12, minute=43)
        self.assertEqual(t.toTimestr(), '-00000002010-01-01T12:43:00Z')
        self.assertEqual(t.toTimestr(force_iso=True), '-2010-01-01T12:43:00Z')

    def test_WbTime_fromTimestr(self):
        """Test WbTime creation from UTC date/time string."""
        repo = self.get_repo()
        t = pywikibot.WbTime.fromTimestr('+00000002010-01-01T12:43:00Z',
                                         site=repo)
        self.assertEqual(t, pywikibot.WbTime(site=repo, year=2010, hour=12,
                                             minute=43, precision=14))

    def test_WbTime_zero_month(self):
        """Test WbTime creation from date/time string with zero month."""
        # ensures we support formats in T123888 / T107870
        repo = self.get_repo()
        t = pywikibot.WbTime.fromTimestr('+00000002010-00-00T12:43:00Z',
                                         site=repo)
        self.assertEqual(t, pywikibot.WbTime(site=repo, year=2010, month=0,
                                             day=0, hour=12, minute=43,
                                             precision=14))

    def test_WbTime_skip_params_precision(self):
        """Test skipping units (such as day, month) when creating WbTimes."""
        repo = self.get_repo()
        t = pywikibot.WbTime(year=2020, day=2, site=repo)
        self.assertEqual(t, pywikibot.WbTime(year=2020, month=1, day=2,
                                             site=repo))
        self.assertEqual(t.precision, pywikibot.WbTime.PRECISION['day'])
        t2 = pywikibot.WbTime(year=2020, hour=5, site=repo)
        self.assertEqual(t2, pywikibot.WbTime(year=2020, month=1, day=1,
                                              hour=5, site=repo))
        self.assertEqual(t2.precision, pywikibot.WbTime.PRECISION['hour'])
        t3 = pywikibot.WbTime(year=2020, minute=5, site=repo)
        self.assertEqual(t3, pywikibot.WbTime(year=2020, month=1, day=1,
                                              hour=0, minute=5, site=repo))
        self.assertEqual(t3.precision, pywikibot.WbTime.PRECISION['minute'])
        t4 = pywikibot.WbTime(year=2020, second=5, site=repo)
        self.assertEqual(t4, pywikibot.WbTime(year=2020, month=1, day=1,
                                              hour=0, minute=0, second=5,
                                              site=repo))
        self.assertEqual(t4.precision, pywikibot.WbTime.PRECISION['second'])
        t5 = pywikibot.WbTime(year=2020, month=2, hour=5, site=repo)
        self.assertEqual(t5, pywikibot.WbTime(year=2020, month=2, day=1,
                                              hour=5, site=repo))
        self.assertEqual(t5.precision, pywikibot.WbTime.PRECISION['hour'])
        t6 = pywikibot.WbTime(year=2020, month=2, minute=5, site=repo)
        self.assertEqual(t6, pywikibot.WbTime(year=2020, month=2, day=1,
                                              hour=0, minute=5, site=repo))
        self.assertEqual(t6.precision, pywikibot.WbTime.PRECISION['minute'])
        t7 = pywikibot.WbTime(year=2020, month=2, second=5, site=repo)
        self.assertEqual(t7, pywikibot.WbTime(year=2020, month=2, day=1,
                                              hour=0, minute=0, second=5,
                                              site=repo))
        self.assertEqual(t7.precision, pywikibot.WbTime.PRECISION['second'])
        t8 = pywikibot.WbTime(year=2020, day=2, hour=5, site=repo)
        self.assertEqual(t8, pywikibot.WbTime(year=2020, month=1, day=2,
                                              hour=5, site=repo))
        self.assertEqual(t8.precision, pywikibot.WbTime.PRECISION['hour'])
        t9 = pywikibot.WbTime(year=2020, month=3, day=2, minute=5, site=repo)
        self.assertEqual(t9, pywikibot.WbTime(year=2020, month=3, day=2,
                                              hour=0, minute=5, site=repo))
        self.assertEqual(t9.precision, pywikibot.WbTime.PRECISION['minute'])

    def test_WbTime_normalization(self):
        """Test WbTime normalization."""
        repo = self.get_repo()
        # flake8 is being annoying, so to reduce line length, I'll make
        # some aliases here
        decade = pywikibot.WbTime.PRECISION['decade']
        century = pywikibot.WbTime.PRECISION['century']
        millennium = pywikibot.WbTime.PRECISION['millennium']
        t = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                             minute=43, second=12)
        t2 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['second'])
        t3 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['minute'])
        t4 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['hour'])
        t5 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['day'])
        t6 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['month'])
        t7 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=pywikibot.WbTime.PRECISION['year'])
        t8 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=decade)
        t9 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                              minute=43, second=12,
                              precision=century)
        t10 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                               minute=43, second=12,
                               precision=millennium)
        t11 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                               minute=43, second=12, timezone=-300,
                               precision=pywikibot.WbTime.PRECISION['day'])
        t12 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                               minute=43, second=12, timezone=300,
                               precision=pywikibot.WbTime.PRECISION['day'])
        t13 = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                               minute=43, second=12, timezone=-300,
                               precision=pywikibot.WbTime.PRECISION['hour'])
        self.assertEqual(t.normalize(), t)
        self.assertEqual(t2.normalize(), t.normalize())
        self.assertEqual(t3.normalize(),
                         pywikibot.WbTime(site=repo, year=2010, month=1,
                                          day=1, hour=12, minute=43))
        self.assertEqual(t4.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                                          month=1, day=1, hour=12))
        self.assertEqual(t5.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                                          month=1, day=1))
        self.assertEqual(t6.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                                          month=1))
        self.assertEqual(
            t7.normalize(), pywikibot.WbTime(site=repo, year=2010))
        self.assertEqual(t8.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                         precision=decade))
        self.assertEqual(t9.normalize(),
                         pywikibot.WbTime(site=repo, year=2100,
                         precision=century))
        self.assertEqual(t9.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                                          precision=century).normalize())
        self.assertEqual(t10.normalize(),
                         pywikibot.WbTime(site=repo, year=3000,
                                          precision=millennium))
        self.assertEqual(t10.normalize(),
                         pywikibot.WbTime(site=repo, year=2010,
                                          precision=millennium).normalize())
        t11_normalized = t11.normalize()
        t12_normalized = t12.normalize()
        self.assertEqual(t11_normalized.timezone, 0)
        self.assertEqual(t12_normalized.timezone, 0)
        self.assertNotEqual(t11, t12)
        self.assertEqual(t11_normalized, t12_normalized)
        self.assertEqual(t13.normalize().timezone, -300)

    def test_WbTime_normalization_very_low_precision(self):
        """Test WbTime normalization with very low precision."""
        repo = self.get_repo()
        # flake8 is being annoying, so to reduce line length, I'll make
        # some aliases here
        year_10000 = pywikibot.WbTime.PRECISION['10000']
        year_100000 = pywikibot.WbTime.PRECISION['100000']
        year_1000000 = pywikibot.WbTime.PRECISION['1000000']
        year_10000000 = pywikibot.WbTime.PRECISION['10000000']
        year_100000000 = pywikibot.WbTime.PRECISION['100000000']
        year_1000000000 = pywikibot.WbTime.PRECISION['1000000000']
        t = pywikibot.WbTime(site=repo, year=-3124684989,
                             precision=year_10000)
        t2 = pywikibot.WbTime(site=repo, year=-3124684989,
                              precision=year_100000)
        t3 = pywikibot.WbTime(site=repo, year=-3124684989,
                              precision=year_1000000)
        t4 = pywikibot.WbTime(site=repo, year=-3124684989,
                              precision=year_10000000)
        t5 = pywikibot.WbTime(site=repo, year=-3124684989,
                              precision=year_100000000)
        t6 = pywikibot.WbTime(site=repo, year=-3124684989,
                              precision=year_1000000000)
        self.assertEqual(t.normalize(),
                         pywikibot.WbTime(site=repo, year=-3124680000,
                         precision=year_10000))
        self.assertEqual(t2.normalize(),
                         pywikibot.WbTime(site=repo, year=-3124700000,
                                          precision=year_100000))
        self.assertEqual(t3.normalize(),
                         pywikibot.WbTime(site=repo, year=-3125000000,
                                          precision=year_1000000))
        self.assertEqual(t4.normalize(),
                         pywikibot.WbTime(site=repo, year=-3120000000,
                                          precision=year_10000000))
        self.assertEqual(t5.normalize(),
                         pywikibot.WbTime(site=repo, year=-3100000000,
                                          precision=year_100000000))
        self.assertEqual(t6.normalize(),
                         pywikibot.WbTime(site=repo, year=-3000000000,
                                          precision=year_1000000000))

    def test_WbTime_timestamp(self):
        """Test timestamp functions of WbTime."""
        repo = self.get_repo()
        timestamp = pywikibot.Timestamp.fromISOformat('2010-01-01T12:43:00Z')
        t = pywikibot.WbTime(site=repo, year=2010, month=0, day=0, hour=12,
                             minute=43)
        self.assertEqual(t.toTimestamp(), timestamp)

        # Roundtrip fails as Timestamp and WbTime interpret month=0 differently
        self.assertNotEqual(
            t, pywikibot.WbTime.fromTimestamp(timestamp, site=repo))

        t = pywikibot.WbTime(site=repo, year=2010, hour=12, minute=43)
        self.assertEqual(t.toTimestamp(), timestamp)

        t = pywikibot.WbTime(site=repo, year=-2010, hour=12, minute=43)
        regex = r'^You cannot turn BC dates into a Timestamp$'
        with self.assertRaisesRegex(ValueError, regex):
            t.toTimestamp()

        t = pywikibot.WbTime(site=repo, year=2010, month=1, day=1, hour=12,
                             minute=43, second=0)
        self.assertEqual(t.toTimestamp(), timestamp)
        self.assertEqual(
            t, pywikibot.WbTime.fromTimestamp(timestamp, site=repo))
        timezone = datetime.timezone(datetime.timedelta(hours=-5))
        ts = pywikibot.Timestamp(2020, 1, 1, 12, 43, 0, tzinfo=timezone)
        t = pywikibot.WbTime.fromTimestamp(ts, site=repo, copy_timezone=True)
        self.assertEqual(t.timezone, -5 * 60)
        t = pywikibot.WbTime.fromTimestamp(ts, site=repo, copy_timezone=True,
                                           timezone=60)
        self.assertEqual(t.timezone, 60)

        ts1 = pywikibot.Timestamp(
            year=2022, month=12, day=21, hour=13,
            tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))
        t1 = pywikibot.WbTime.fromTimestamp(ts1, timezone=-300, site=repo)
        self.assertIsNotNone(t1.toTimestamp(timezone_aware=True).tzinfo)
        self.assertIsNone(t1.toTimestamp(timezone_aware=False).tzinfo)
        self.assertEqual(t1.toTimestamp(timezone_aware=True), ts1)
        self.assertNotEqual(t1.toTimestamp(timezone_aware=False), ts1)

    def test_WbTime_errors(self):
        """Test WbTime precision errors."""
        repo = self.get_repo()
        regex = r'^no year given$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTime(site=repo, precision=15)
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTime(site=repo, precision='invalid_precision')
        regex = r'^Invalid precision: "15"$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTime(site=repo, year=2020, precision=15)
        regex = r'^Invalid precision: "invalid_precision"$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTime(site=repo, year=2020,
                             precision='invalid_precision')

    def test_comparison(self):
        """Test WbTime comparison."""
        repo = self.get_repo()
        t1 = pywikibot.WbTime(site=repo, year=2010, hour=12, minute=43)
        t2 = pywikibot.WbTime(site=repo, year=-2005, hour=16, minute=45)
        self.assertEqual(t1.precision, pywikibot.WbTime.PRECISION['minute'])
        self.assertEqual(t1, t1)
        self.assertGreaterEqual(t1, t1)
        self.assertGreaterEqual(t1, t2)
        self.assertGreater(t1, t2)
        self.assertEqual(t1.year, 2010)
        self.assertEqual(t2.year, -2005)
        self.assertEqual(t1.month, 1)
        self.assertEqual(t2.month, 1)
        self.assertEqual(t1.day, 1)
        self.assertEqual(t2.day, 1)
        self.assertEqual(t1.hour, 12)
        self.assertEqual(t2.hour, 16)
        self.assertEqual(t1.minute, 43)
        self.assertEqual(t2.minute, 45)
        self.assertEqual(t1.second, 0)
        self.assertEqual(t2.second, 0)
        self.assertEqual(t1.toTimestr(), '+00000002010-01-01T12:43:00Z')
        self.assertEqual(t2.toTimestr(), '-00000002005-01-01T16:45:00Z')
        self.assertRaises(ValueError, pywikibot.WbTime, site=repo,
                          precision=15)
        self.assertRaises(ValueError, pywikibot.WbTime, site=repo,
                          precision='invalid_precision')
        self.assertIsInstance(t1.toTimestamp(), pywikibot.Timestamp)
        self.assertRaises(ValueError, t2.toTimestamp)

    def test_comparison_types(self):
        """Test WbTime comparison with different types."""
        repo = self.get_repo()
        t1 = pywikibot.WbTime(site=repo, year=2010, hour=12, minute=43)
        t2 = pywikibot.WbTime(site=repo, year=-2005, hour=16, minute=45)
        self.assertGreater(t1, t2)
        self.assertRaises(TypeError, operator.lt, t1, 5)
        self.assertRaises(TypeError, operator.gt, t1, 5)
        self.assertRaises(TypeError, operator.le, t1, 5)
        self.assertRaises(TypeError, operator.ge, t1, 5)

    def test_comparison_timezones(self):
        """Test comparisons with timezones."""
        repo = self.get_repo()
        ts1 = pywikibot.Timestamp(
            year=2022, month=12, day=21, hour=13,
            tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))
        ts2 = pywikibot.Timestamp(
            year=2022, month=12, day=21, hour=17,
            tzinfo=datetime.timezone.utc)
        self.assertGreater(ts1.timestamp(), ts2.timestamp())

        t1 = pywikibot.WbTime.fromTimestamp(ts1, timezone=-300, site=repo)
        t2 = pywikibot.WbTime.fromTimestamp(ts2, timezone=0, site=repo)
        self.assertGreater(t1, t2)

    def test_comparison_timezones_equal(self):
        """Test when two WbTime's have equal instants but not the same tz."""
        repo = self.get_repo()
        ts1 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=13,
            tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))
        ts2 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=18,
            tzinfo=datetime.timezone.utc)
        self.assertEqual(ts1.timestamp(), ts2.timestamp())

        t1 = pywikibot.WbTime.fromTimestamp(ts1, timezone=-300, site=repo)
        t2 = pywikibot.WbTime.fromTimestamp(ts2, timezone=0, site=repo)
        self.assertGreaterEqual(t1, t2)
        self.assertGreaterEqual(t2, t1)
        self.assertNotEqual(t1, t2)
        self.assertNotEqual(t2, t1)
        # Ignore H205: We specifically want to test the operator
        self.assertFalse(t1 > t2)  # noqa: H205
        self.assertFalse(t2 > t1)  # noqa: H205
        self.assertFalse(t1 < t2)  # noqa: H205
        self.assertFalse(t2 < t1)  # noqa: H205

    def test_comparison_equal_instant(self):
        """Test the equal_instant method."""
        repo = self.get_repo()

        ts1 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=13,
            tzinfo=datetime.timezone(datetime.timedelta(hours=-5)))
        ts2 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=18,
            tzinfo=datetime.timezone.utc)
        ts3 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=19,
            tzinfo=datetime.timezone(datetime.timedelta(hours=1)))
        ts4 = pywikibot.Timestamp(
            year=2023, month=12, day=21, hour=13,
            tzinfo=datetime.timezone(datetime.timedelta(hours=-6)))

        self.assertEqual(ts1.timestamp(), ts2.timestamp())
        self.assertEqual(ts1.timestamp(), ts3.timestamp())
        self.assertEqual(ts2.timestamp(), ts3.timestamp())
        self.assertNotEqual(ts1.timestamp(), ts4.timestamp())
        self.assertNotEqual(ts2.timestamp(), ts4.timestamp())
        self.assertNotEqual(ts3.timestamp(), ts4.timestamp())

        t1 = pywikibot.WbTime.fromTimestamp(ts1, timezone=-300, site=repo)
        t2 = pywikibot.WbTime.fromTimestamp(ts2, timezone=0, site=repo)
        t3 = pywikibot.WbTime.fromTimestamp(ts3, timezone=60, site=repo)
        t4 = pywikibot.WbTime.fromTimestamp(ts4, timezone=-360, site=repo)

        self.assertTrue(t1.equal_instant(t2))
        self.assertTrue(t1.equal_instant(t3))
        self.assertTrue(t2.equal_instant(t3))
        self.assertFalse(t1.equal_instant(t4))
        self.assertFalse(t2.equal_instant(t4))
        self.assertFalse(t3.equal_instant(t4))


class TestWbQuantity(WbRepresentationTestCase):

    """Test Wikibase WbQuantity data type."""

    dry = True

    def test_WbQuantity_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=1234, error=1, site=repo)
        self._test_hashable(q)

    def test_WbQuantity_integer(self):
        """Test WbQuantity for integer value."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=1234, error=1, site=repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+1234', 'lowerBound': '+1233',
                          'upperBound': '+1235', 'unit': '1'})
        q = pywikibot.WbQuantity(amount=5, error=(2, 3), site=repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+5', 'lowerBound': '+2',
                          'upperBound': '+7', 'unit': '1'})
        q = pywikibot.WbQuantity(amount=0, error=(0, 0), site=repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+0', 'lowerBound': '+0',
                          'upperBound': '+0', 'unit': '1'})
        q = pywikibot.WbQuantity(amount=-5, error=(2, 3), site=repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '-5', 'lowerBound': '-8',
                          'upperBound': '-3', 'unit': '1'})

    def test_WbQuantity_float_27(self):
        """Test WbQuantity for float value."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=0.044405586, error=0.0, site=repo)
        q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586',
                  'upperBound': '+0.044405586', 'unit': '1'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbQuantity_scientific(self):
        """Test WbQuantity for scientific notation."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount='1.3e-13', error='1e-14', site=repo)
        q_dict = {'amount': '+1.3e-13', 'lowerBound': '+1.2e-13',
                  'upperBound': '+1.4e-13', 'unit': '1'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbQuantity_decimal(self):
        """Test WbQuantity for decimal value."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=Decimal('0.044405586'),
                                 error=Decimal('0.0'), site=repo)
        q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586',
                  'upperBound': '+0.044405586', 'unit': '1'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbQuantity_string(self):
        """Test WbQuantity for decimal notation."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo)
        q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586',
                  'upperBound': '+0.044405586', 'unit': '1'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbQuantity_formatting_bound(self):
        """Test WbQuantity formatting with bounds."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo)
        self.assertEqual(str(q),
                         '{{\n'
                         '    "amount": "+{val}",\n'
                         '    "lowerBound": "+{val}",\n'
                         '    "unit": "1",\n'
                         '    "upperBound": "+{val}"\n'
                         '}}'.format(val='0.044405586'))
        self.assertEqual(repr(q),
                         'WbQuantity(amount={val}, '
                         'upperBound={val}, lowerBound={val}, '
                         'unit=1)'.format(val='0.044405586'))

    def test_WbQuantity_self_equality(self):
        """Test WbQuantity equality."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo)
        self.assertEqual(q, q)

    def test_WbQuantity_fromWikibase(self):
        """Test WbQuantity.fromWikibase() instantiating."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity.fromWikibase({'amount': '+0.0229',
                                               'lowerBound': '0',
                                               'upperBound': '1',
                                               'unit': '1'},
                                              site=repo)
        # note that the bounds are inputted as INT but are returned as FLOAT
        self.assertEqual(q.toWikibase(),
                         {'amount': '+0.0229', 'lowerBound': '+0.0000',
                          'upperBound': '+1.0000', 'unit': '1'})

    def test_WbQuantity_errors(self):
        """Test WbQuantity error handling."""
        regex = r'^no amount given$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbQuantity(amount=None, error=1)

    def test_WbQuantity_entity_unit(self):
        """Test WbQuantity with entity uri unit."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=1234, error=1, site=repo,
                                 unit='http://www.wikidata.org/entity/Q712226')
        self.assertEqual(q.toWikibase(),
                         {'amount': '+1234', 'lowerBound': '+1233',
                          'upperBound': '+1235',
                          'unit': 'http://www.wikidata.org/entity/Q712226'})

    def test_WbQuantity_unit_fromWikibase(self):
        """Test WbQuantity recognising unit from Wikibase output."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity.fromWikibase({
            'amount': '+1234', 'lowerBound': '+1233', 'upperBound': '+1235',
            'unit': 'http://www.wikidata.org/entity/Q712226'},
            site=repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+1234', 'lowerBound': '+1233',
                          'upperBound': '+1235',
                          'unit': 'http://www.wikidata.org/entity/Q712226'})


class TestWbQuantityNonDry(WbRepresentationTestCase):

    """Test Wikibase WbQuantity data type (non-dry).

    These can be moved to TestWbQuantity once DrySite has been bumped to
    the appropriate version.
    """

    def setUp(self):
        """Override setup to store repo and it's version."""
        super().setUp()
        self.repo = self.get_repo()

    def test_WbQuantity_unbound(self):
        """Test WbQuantity for value without bounds."""
        q = pywikibot.WbQuantity(amount=1234.5, site=self.repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+1234.5', 'unit': '1',
                          'upperBound': None, 'lowerBound': None})

    def test_WbQuantity_formatting_unbound(self):
        """Test WbQuantity formatting without bounds."""
        q = pywikibot.WbQuantity(amount='0.044405586', site=self.repo)
        self.assertEqual(str(q),
                         '{{\n'
                         '    "amount": "+{val}",\n'
                         '    "lowerBound": null,\n'
                         '    "unit": "1",\n'
                         '    "upperBound": null\n'
                         '}}'.format(val='0.044405586'))
        self.assertEqual(repr(q),
                         'WbQuantity(amount={val}, '
                         'upperBound=None, lowerBound=None, '
                         'unit=1)'.format(val='0.044405586'))

    def test_WbQuantity_fromWikibase_unbound(self):
        """Test WbQuantity.fromWikibase() instantiating without bounds."""
        q = pywikibot.WbQuantity.fromWikibase({'amount': '+0.0229',
                                               'unit': '1'},
                                              site=self.repo)
        self.assertEqual(q.toWikibase(),
                         {'amount': '+0.0229', 'lowerBound': None,
                          'upperBound': None, 'unit': '1'})

    def test_WbQuantity_ItemPage_unit(self):
        """Test WbQuantity with ItemPage unit."""
        q = pywikibot.WbQuantity(amount=1234, error=1,
                                 unit=pywikibot.ItemPage(self.repo, 'Q712226'))
        self.assertEqual(q.toWikibase(),
                         {'amount': '+1234', 'lowerBound': '+1233',
                          'upperBound': '+1235',
                          'unit': 'http://www.wikidata.org/entity/Q712226'})

    def test_WbQuantity_equality(self):
        """Test WbQuantity equality with different unit representations."""
        a = pywikibot.WbQuantity(
            amount=1234, error=1,
            unit=pywikibot.ItemPage(self.repo, 'Q712226'))
        b = pywikibot.WbQuantity(
            amount=1234, error=1,
            unit='http://www.wikidata.org/entity/Q712226')
        c = pywikibot.WbQuantity(
            amount=1234, error=1,
            unit='http://test.wikidata.org/entity/Q712226')
        d = pywikibot.WbQuantity(
            amount=1234, error=2,
            unit='http://www.wikidata.org/entity/Q712226')
        self.assertEqual(a, b)
        self.assertNotEqual(a, c)
        self.assertNotEqual(b, c)
        self.assertNotEqual(b, d)

    def test_WbQuantity_get_unit_item(self):
        """Test getting unit item from WbQuantity."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=1234, error=1, site=repo,
                                 unit='http://www.wikidata.org/entity/Q123')
        self.assertEqual(q.get_unit_item(),
                         ItemPage(repo, 'Q123'))

    def test_WbQuantity_get_unit_item_provide_repo(self):
        """Test getting unit item from WbQuantity, providing repo."""
        repo = self.get_repo()
        q = pywikibot.WbQuantity(amount=1234, error=1,
                                 unit='http://www.wikidata.org/entity/Q123')
        self.assertEqual(q.get_unit_item(repo),
                         ItemPage(repo, 'Q123'))

    def test_WbQuantity_get_unit_item_different_repo(self):
        """Test getting unit item in different repo from WbQuantity."""
        repo = self.get_repo()
        test_repo = pywikibot.Site('test', 'wikidata')
        q = pywikibot.WbQuantity(amount=1234, error=1, site=repo,
                                 unit='http://test.wikidata.org/entity/Q123')
        self.assertEqual(q.get_unit_item(test_repo),
                         ItemPage(test_repo, 'Q123'))


class TestWbMonolingualText(WbRepresentationTestCase):

    """Test Wikibase WbMonolingualText data type."""

    dry = True

    def test_WbMonolingualText_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        q = pywikibot.WbMonolingualText(
            text='Test that basics work', language='en')
        self._test_hashable(q)

    def test_WbMonolingualText_string(self):
        """Test WbMonolingualText string."""
        q = pywikibot.WbMonolingualText(text='Test that basics work',
                                        language='en')
        q_dict = {'text': 'Test that basics work', 'language': 'en'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbMonolingualText_unicode(self):
        """Test WbMonolingualText unicode."""
        q = pywikibot.WbMonolingualText(text='Testa det här', language='sv')
        q_dict = {'text': 'Testa det här', 'language': 'sv'}
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbMonolingualText_equality(self):
        """Test WbMonolingualText equality."""
        q = pywikibot.WbMonolingualText(text='Thou shall test this!',
                                        language='en-gb')
        self.assertEqual(q, q)

    def test_WbMonolingualText_fromWikibase(self):
        """Test WbMonolingualText.fromWikibase() instantiating."""
        q = pywikibot.WbMonolingualText.fromWikibase({'text': 'Test this!',
                                                      'language': 'en'})
        self.assertEqual(q.toWikibase(),
                         {'text': 'Test this!', 'language': 'en'})

    def test_WbMonolingualText_errors(self):
        """Test WbMonolingualText error handling."""
        regex = r'^text and language cannot be empty$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbMonolingualText(text='', language='sv')
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbMonolingualText(text='Test this!', language='')
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbMonolingualText(text=None, language='sv')


class TestWbGeoShapeNonDry(WbRepresentationTestCase):

    """Test Wikibase WbGeoShape data type (non-dry).

    These require non dry tests due to the page.exists() call.
    """

    def setUp(self):
        """Setup tests."""
        self.commons = pywikibot.Site('commons')
        self.page = Page(self.commons, 'Data:Lyngby Hovedgade.map')
        super().setUp()

    def test_WbGeoShape_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        q = pywikibot.WbGeoShape(self.page)
        self._test_hashable(q)

    def test_WbGeoShape_page(self):
        """Test WbGeoShape page."""
        q = pywikibot.WbGeoShape(self.page)
        q_val = 'Data:Lyngby Hovedgade.map'
        self.assertEqual(q.toWikibase(), q_val)

    def test_WbGeoShape_page_and_site(self):
        """Test WbGeoShape from page and site."""
        q = pywikibot.WbGeoShape(self.page, self.get_repo())
        q_val = 'Data:Lyngby Hovedgade.map'
        self.assertEqual(q.toWikibase(), q_val)

    def test_WbGeoShape_equality(self):
        """Test WbGeoShape equality."""
        q = pywikibot.WbGeoShape(self.page, self.get_repo())
        self.assertEqual(q, q)

    def test_WbGeoShape_fromWikibase(self):
        """Test WbGeoShape.fromWikibase() instantiating."""
        repo = self.get_repo()
        q = pywikibot.WbGeoShape.fromWikibase(
            'Data:Lyngby Hovedgade.map', repo)
        self.assertEqual(q.toWikibase(), 'Data:Lyngby Hovedgade.map')

    def test_WbGeoShape_error_on_non_page(self):
        """Test WbGeoShape error handling when given a non-page."""
        regex = r'^Page .+? must be a pywikibot\.Page object not a'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbGeoShape('A string', self.get_repo())

    def test_WbGeoShape_error_on_non_exitant_page(self):
        """Test WbGeoShape error handling of a non-existent page."""
        page = Page(self.commons, 'Non-existent page... really')
        regex = r'^Page \[\[.+?\]\] must exist\.$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbGeoShape(page, self.get_repo())

    def test_WbGeoShape_error_on_wrong_site(self):
        """Test WbGeoShape error handling of a page on non-filerepo site."""
        repo = self.get_repo()
        page = Page(repo, 'Q123')
        regex = r'^Page must be on the geo-shape repository site\.$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbGeoShape(page, self.get_repo())

    def test_WbGeoShape_error_on_wrong_page_type(self):
        """Test WbGeoShape error handling of a non-map page."""
        non_data_page = Page(self.commons, 'File:Foo.jpg')
        non_map_page = Page(self.commons, 'Data:TemplateData/TemplateData.tab')
        regex = (r"^Page must be in 'Data:' namespace and end in '\.map' "
                 r'for geo-shape\.$')
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbGeoShape(non_data_page, self.get_repo())
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbGeoShape(non_map_page, self.get_repo())


class TestWbTabularDataNonDry(WbRepresentationTestCase):

    """Test Wikibase WbTabularData data type (non-dry).

    These require non dry tests due to the page.exists() call.
    """

    def setUp(self):
        """Setup tests."""
        self.commons = pywikibot.Site('commons')
        self.page = Page(self.commons, 'Data:Bea.gov/GDP by state.tab')
        super().setUp()

    def test_WbTabularData_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        q = pywikibot.WbTabularData(self.page)
        self._test_hashable(q)

    def test_WbTabularData_page(self):
        """Test WbTabularData page."""
        q = pywikibot.WbTabularData(self.page)
        q_val = 'Data:Bea.gov/GDP by state.tab'
        self.assertEqual(q.toWikibase(), q_val)

    def test_WbTabularData_page_and_site(self):
        """Test WbTabularData from page and site."""
        q = pywikibot.WbTabularData(self.page, self.get_repo())
        q_val = 'Data:Bea.gov/GDP by state.tab'
        self.assertEqual(q.toWikibase(), q_val)

    def test_WbTabularData_equality(self):
        """Test WbTabularData equality."""
        q = pywikibot.WbTabularData(self.page, self.get_repo())
        self.assertEqual(q, q)

    def test_WbTabularData_fromWikibase(self):
        """Test WbTabularData.fromWikibase() instantiating."""
        repo = self.get_repo()
        q = pywikibot.WbTabularData.fromWikibase(
            'Data:Bea.gov/GDP by state.tab', repo)
        self.assertEqual(q.toWikibase(), 'Data:Bea.gov/GDP by state.tab')

    def test_WbTabularData_error_on_non_page(self):
        """Test WbTabularData error handling when given a non-page."""
        regex = r'^Page .+? must be a pywikibot\.Page object not a'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTabularData('A string', self.get_repo())

    def test_WbTabularData_error_on_non_exitant_page(self):
        """Test WbTabularData error handling of a non-existent page."""
        page = Page(self.commons, 'Non-existent page... really')
        regex = r'^Page \[\[.+?\]\] must exist\.$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTabularData(page, self.get_repo())

    def test_WbTabularData_error_on_wrong_site(self):
        """Test WbTabularData error handling of a page on non-filerepo site."""
        repo = self.get_repo()
        page = Page(repo, 'Q123')
        regex = r'^Page must be on the tabular-data repository site\.$'
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTabularData(page, self.get_repo())

    def test_WbTabularData_error_on_wrong_page_type(self):
        """Test WbTabularData error handling of a non-map page."""
        non_data_page = Page(self.commons, 'File:Foo.jpg')
        non_map_page = Page(self.commons, 'Data:Lyngby Hovedgade.map')
        regex = (r"^Page must be in 'Data:' namespace and end in '\.tab' "
                 r'for tabular-data\.$')
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTabularData(non_data_page, self.get_repo())
        with self.assertRaisesRegex(ValueError, regex):
            pywikibot.WbTabularData(non_map_page, self.get_repo())


class TestWbUnknown(WbRepresentationTestCase):

    """Test Wikibase WbUnknown data type."""

    dry = True

    def test_WbUnknown_WbRepresentation_methods(self):
        """Test inherited or extended methods from _WbRepresentation."""
        q_dict = {'text': 'Test that basics work', 'language': 'en'}
        q = pywikibot.WbUnknown(q_dict)
        self._test_hashable(q)

    def test_WbUnknown_string(self):
        """Test WbUnknown string."""
        q_dict = {'text': 'Test that basics work', 'language': 'en'}
        q = pywikibot.WbUnknown(q_dict)
        self.assertEqual(q.toWikibase(), q_dict)

    def test_WbUnknown_equality(self):
        """Test WbUnknown equality."""
        q_dict = {'text': 'Thou shall test this!', 'language': 'unknown'}
        q = pywikibot.WbUnknown(q_dict)
        self.assertEqual(q, q)

    def test_WbUnknown_fromWikibase(self):
        """Test WbUnknown.fromWikibase() instantiating."""
        q = pywikibot.WbUnknown.fromWikibase({'text': 'Test this!',
                                              'language': 'en'})
        self.assertEqual(q.toWikibase(),
                         {'text': 'Test this!', 'language': 'en'})


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