diff options
| author | 2019-12-13 13:25:25 +1000 | |
|---|---|---|
| committer | 2019-12-13 13:25:25 +1000 | |
| commit | d61317fb347b75470bf2cc3d66a4ddda5e282908 (patch) | |
| tree | 447499587a72106d5a6adf4783afee986d9ff614 | |
| parent | Revert "Use OAuth to be Reddit API compliant" (#695) (diff) | |
| parent | Fixed a typo ( due to poor copy pasta and eyeballing skills ) (diff) | |
Merge pull request #680 from python-discord/Write-unit-tests-for-`bot/utils/time.py`
Added `unittest` for `bot.utils.time`
| -rw-r--r-- | bot/utils/time.py | 11 | ||||
| -rw-r--r-- | tests/bot/utils/test_time.py | 162 | 
2 files changed, 170 insertions, 3 deletions
| diff --git a/bot/utils/time.py b/bot/utils/time.py index ac64865d6..7416f36e0 100644 --- a/bot/utils/time.py +++ b/bot/utils/time.py @@ -115,7 +115,7 @@ def format_infraction(timestamp: str) -> str:  def format_infraction_with_duration(      expiry: Optional[str], -    date_from: datetime.datetime = None, +    date_from: Optional[datetime.datetime] = None,      max_units: int = 2  ) -> Optional[str]:      """ @@ -140,10 +140,15 @@ def format_infraction_with_duration(      return f"{expiry_formatted}{duration_formatted}" -def until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]: +def until_expiration( +    expiry: Optional[str], +    now: Optional[datetime.datetime] = None, +    max_units: int = 2 +) -> Optional[str]:      """      Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta. +    Returns a human-readable version of the remaining duration between datetime.utcnow() and an expiry.      Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.      `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).      By default, max_units is 2. @@ -151,7 +156,7 @@ def until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]      if not expiry:          return None -    now = datetime.datetime.utcnow() +    now = now or datetime.datetime.utcnow()      since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)      if since < now: diff --git a/tests/bot/utils/test_time.py b/tests/bot/utils/test_time.py new file mode 100644 index 000000000..69f35f2f5 --- /dev/null +++ b/tests/bot/utils/test_time.py @@ -0,0 +1,162 @@ +import asyncio +import unittest +from datetime import datetime, timezone +from unittest.mock import patch + +from dateutil.relativedelta import relativedelta + +from bot.utils import time +from tests.helpers import AsyncMock + + +class TimeTests(unittest.TestCase): +    """Test helper functions in bot.utils.time.""" + +    def test_humanize_delta_handle_unknown_units(self): +        """humanize_delta should be able to handle unknown units, and will not abort.""" +        # Does not abort for unknown units, as the unit name is checked +        # against the attribute of the relativedelta instance. +        self.assertEqual(time.humanize_delta(relativedelta(days=2, hours=2), 'elephants', 2), '2 days and 2 hours') + +    def test_humanize_delta_handle_high_units(self): +        """humanize_delta should be able to handle very high units.""" +        # Very high maximum units, but it only ever iterates over +        # each value the relativedelta might have. +        self.assertEqual(time.humanize_delta(relativedelta(days=2, hours=2), 'hours', 20), '2 days and 2 hours') + +    def test_humanize_delta_should_normal_usage(self): +        """Testing humanize delta.""" +        test_cases = ( +            (relativedelta(days=2), 'seconds', 1, '2 days'), +            (relativedelta(days=2, hours=2), 'seconds', 2, '2 days and 2 hours'), +            (relativedelta(days=2, hours=2), 'seconds', 1, '2 days'), +            (relativedelta(days=2, hours=2), 'days', 2, '2 days'), +        ) + +        for delta, precision, max_units, expected in test_cases: +            with self.subTest(delta=delta, precision=precision, max_units=max_units, expected=expected): +                self.assertEqual(time.humanize_delta(delta, precision, max_units), expected) + +    def test_humanize_delta_raises_for_invalid_max_units(self): +        """humanize_delta should raises ValueError('max_units must be positive') for invalid max_units.""" +        test_cases = (-1, 0) + +        for max_units in test_cases: +            with self.subTest(max_units=max_units), self.assertRaises(ValueError) as error: +                time.humanize_delta(relativedelta(days=2, hours=2), 'hours', max_units) +                self.assertEqual(str(error), 'max_units must be positive') + +    def test_parse_rfc1123(self): +        """Testing parse_rfc1123.""" +        self.assertEqual( +            time.parse_rfc1123('Sun, 15 Sep 2019 12:00:00 GMT'), +            datetime(2019, 9, 15, 12, 0, 0, tzinfo=timezone.utc) +        ) + +    def test_format_infraction(self): +        """Testing format_infraction.""" +        self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'), '2019-12-12 00:01') + +    @patch('asyncio.sleep', new_callable=AsyncMock) +    def test_wait_until(self, mock): +        """Testing wait_until.""" +        start = datetime(2019, 1, 1, 0, 0) +        then = datetime(2019, 1, 1, 0, 10) + +        # No return value +        self.assertIs(asyncio.run(time.wait_until(then, start)), None) + +        mock.assert_called_once_with(10 * 60) + +    def test_format_infraction_with_duration_none_expiry(self): +        """format_infraction_with_duration should work for None expiry.""" +        test_cases = ( +            (None, None, None, None), + +            # To make sure that date_from and max_units are not touched +            (None, 'Why hello there!', None, None), +            (None, None, float('inf'), None), +            (None, 'Why hello there!', float('inf'), None), +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_format_infraction_with_duration_custom_units(self): +        """format_infraction_with_duration should work for custom max_units.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6, +             '2019-12-12 00:01 (11 hours, 55 minutes and 55 seconds)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20, +             '2019-11-23 20:09 (6 months, 28 days, 23 hours and 54 minutes)') +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_format_infraction_with_duration_normal_usage(self): +        """format_infraction_with_duration should work for normal usage, across various durations.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '2019-12-12 00:01 (12 hours and 55 seconds)'), +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '2019-12-12 00:01 (12 hours)'), +            ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '2019-12-12 00:00 (1 minute)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '2019-11-23 20:09 (7 days and 23 hours)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '2019-11-23 20:09 (6 months and 28 days)'), +            ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '2019-11-23 20:58 (5 minutes)'), +            ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '2019-11-24 00:00 (1 minute)'), +            ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2019-11-23 23:59 (2 years and 4 months)'), +            ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2, +             '2019-11-23 23:59 (9 minutes and 55 seconds)'), +            (None, datetime(2019, 11, 23, 23, 49, 5), 2, None), +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_until_expiration_with_duration_none_expiry(self): +        """until_expiration should work for None expiry.""" +        test_cases = ( +            (None, None, None, None), + +            # To make sure that now and max_units are not touched +            (None, 'Why hello there!', None, None), +            (None, None, float('inf'), None), +            (None, 'Why hello there!', float('inf'), None), +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) + +    def test_until_expiration_with_duration_custom_units(self): +        """until_expiration should work for custom max_units.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6, '11 hours, 55 minutes and 55 seconds'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20, '6 months, 28 days, 23 hours and 54 minutes') +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) + +    def test_until_expiration_normal_usage(self): +        """until_expiration should work for normal usage, across various durations.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '12 hours and 55 seconds'), +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '12 hours'), +            ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '1 minute'), +            ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '7 days and 23 hours'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '6 months and 28 days'), +            ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '5 minutes'), +            ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '1 minute'), +            ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2 years and 4 months'), +            ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2, '9 minutes and 55 seconds'), +            (None, datetime(2019, 11, 23, 23, 49, 5), 2, None), +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) | 
