diff options
Diffstat (limited to '')
| -rw-r--r-- | tests/cogs/test_information.py | 48 | ||||
| -rw-r--r-- | tests/helpers.py | 4 | ||||
| -rw-r--r-- | tests/test_converters.py | 78 | ||||
| -rw-r--r-- | tests/test_resources.py | 11 | ||||
| -rw-r--r-- | tests/utils/test_time.py | 62 | 
5 files changed, 195 insertions, 8 deletions
| diff --git a/tests/cogs/test_information.py b/tests/cogs/test_information.py index 85b2d092e..184bd2595 100644 --- a/tests/cogs/test_information.py +++ b/tests/cogs/test_information.py @@ -8,6 +8,8 @@ import pytest  from discord import (      CategoryChannel,      Colour, +    Permissions, +    Role,      TextChannel,      VoiceChannel,  ) @@ -66,6 +68,52 @@ def test_roles_info_command(cog, ctx):      assert embed.footer.text == "Total roles: 1" +def test_role_info_command(cog, ctx): +    dummy_role = MagicMock(spec=Role) +    dummy_role.name = "Dummy" +    dummy_role.colour = Colour.blurple() +    dummy_role.id = 112233445566778899 +    dummy_role.position = 10 +    dummy_role.permissions = Permissions(0) +    dummy_role.members = [ctx.author] + +    admin_role = MagicMock(spec=Role) +    admin_role.name = "Admin" +    admin_role.colour = Colour.red() +    admin_role.id = 998877665544332211 +    admin_role.position = 3 +    admin_role.permissions = Permissions(0) +    admin_role.members = [ctx.author] + +    ctx.guild.roles = [dummy_role, admin_role] + +    cog.role_info.can_run = AsyncMock() +    cog.role_info.can_run.return_value = True + +    coroutine = cog.role_info.callback(cog, ctx, dummy_role, admin_role) + +    assert asyncio.run(coroutine) is None + +    assert ctx.send.call_count == 2 + +    (_, dummy_kwargs), (_, admin_kwargs) = ctx.send.call_args_list + +    dummy_embed = dummy_kwargs["embed"] +    admin_embed = admin_kwargs["embed"] + +    assert dummy_embed.title == "Dummy info" +    assert dummy_embed.colour == Colour.blurple() + +    assert dummy_embed.fields[0].value == str(dummy_role.id) +    assert dummy_embed.fields[1].value == f"#{dummy_role.colour.value:0>6x}" +    assert dummy_embed.fields[2].value == "0.63 0.48 218" +    assert dummy_embed.fields[3].value == "1" +    assert dummy_embed.fields[4].value == "10" +    assert dummy_embed.fields[5].value == "0" + +    assert admin_embed.title == "Admin info" +    assert admin_embed.colour == Colour.red() +  # There is no argument passed in here that we can use to test,  # so the return value would change constantly.  @patch('bot.cogs.information.time_since') diff --git a/tests/helpers.py b/tests/helpers.py index 2908294f7..25059fa3a 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -7,6 +7,10 @@ __all__ = ('AsyncMock', 'async_test')  # TODO: Remove me on 3.8 +# Allows you to mock a coroutine. Since the default `__call__` of `MagicMock` +# is not a coroutine, trying to mock a coroutine with it will result in errors +# as the default `__call__` is not awaitable. Use this class for monkeypatching +# coroutines instead.  class AsyncMock(MagicMock):      async def __call__(self, *args, **kwargs):          return super(AsyncMock, self).__call__(*args, **kwargs) diff --git a/tests/test_converters.py b/tests/test_converters.py index 35fc5d88e..f69995ec6 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -8,6 +8,7 @@ from discord.ext.commands import BadArgument  from bot.converters import (      Duration, +    ISODateTime,      TagContentConverter,      TagNameConverter,      ValidPythonIdentifier, @@ -184,3 +185,80 @@ def test_duration_converter_for_invalid(duration: str):      converter = Duration()      with pytest.raises(BadArgument, match=f'`{duration}` is not a valid duration string.'):          asyncio.run(converter.convert(None, duration)) + + +    ("datetime_string", "expected_dt"), +    ( + +        # `YYYY-mm-ddTHH:MM:SSZ` | `YYYY-mm-dd HH:MM:SSZ` +        ('2019-09-02T02:03:05Z', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 02:03:05Z', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM:SS±HH:MM` | `YYYY-mm-dd HH:MM:SS±HH:MM` +        ('2019-09-02T03:18:05+01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 03:18:05+01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02T00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM:SS±HHMM` | `YYYY-mm-dd HH:MM:SS±HHMM` +        ('2019-09-02T03:18:05+0115', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 03:18:05+0115', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02T00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM:SS±HH` | `YYYY-mm-dd HH:MM:SS±HH` +        ('2019-09-02 03:03:05+01', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02T01:03:05-01', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM:SS` | `YYYY-mm-dd HH:MM:SS` +        ('2019-09-02T02:03:05', datetime.datetime(2019, 9, 2, 2, 3, 5)), +        ('2019-09-02 02:03:05', datetime.datetime(2019, 9, 2, 2, 3, 5)), + +        # `YYYY-mm-ddTHH:MM` | `YYYY-mm-dd HH:MM` +        ('2019-11-12T09:15', datetime.datetime(2019, 11, 12, 9, 15)), +        ('2019-11-12 09:15', datetime.datetime(2019, 11, 12, 9, 15)), + +        # `YYYY-mm-dd` +        ('2019-04-01', datetime.datetime(2019, 4, 1)), + +        # `YYYY-mm` +        ('2019-02-01', datetime.datetime(2019, 2, 1)), + +        # `YYYY` +        ('2025', datetime.datetime(2025, 1, 1)), +    ), +) +def test_isodatetime_converter_for_valid(datetime_string: str, expected_dt: datetime.datetime): +    converter = ISODateTime() +    converted_dt = asyncio.run(converter.convert(None, datetime_string)) +    assert converted_dt.tzinfo is None +    assert converted_dt == expected_dt + + +    ("datetime_string"), +    ( +        # Make sure it doesn't interfere with the Duration converter +        ('1Y'), +        ('1d'), +        ('1H'), + +        # Check if it fails when only providing the optional time part +        ('10:10:10'), +        ('10:00'), + +        # Invalid date format +        ('19-01-01'), + +        # Other non-valid strings +        ('fisk the tag master'), +    ), +) +def test_isodatetime_converter_for_invalid(datetime_string: str): +    converter = ISODateTime() +    with pytest.raises( +        BadArgument, +        match=f"`{datetime_string}` is not a valid ISO-8601 datetime string", +    ): +        asyncio.run(converter.convert(None, datetime_string)) diff --git a/tests/test_resources.py b/tests/test_resources.py index 2b17aea64..bcf124f05 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -1,18 +1,13 @@  import json -import mimetypes  from pathlib import Path -from urllib.parse import urlparse  def test_stars_valid(): -    """Validates that `bot/resources/stars.json` contains valid images.""" +    """Validates that `bot/resources/stars.json` contains a list of strings."""      path = Path('bot', 'resources', 'stars.json')      content = path.read_text()      data = json.loads(content) -    for url in data.values(): -        assert urlparse(url).scheme == 'https' - -        mimetype, _ = mimetypes.guess_type(url) -        assert mimetype in ('image/jpeg', 'image/png') +    for name in data: +        assert type(name) is str diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py new file mode 100644 index 000000000..4baa6395c --- /dev/null +++ b/tests/utils/test_time.py @@ -0,0 +1,62 @@ +import asyncio +from datetime import datetime, timezone +from unittest.mock import patch + +import pytest +from dateutil.relativedelta import relativedelta + +from bot.utils import time +from tests.helpers import AsyncMock + + +    ('delta', 'precision', 'max_units', 'expected'), +    ( +        (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'), + +        # Does not abort for unknown units, as the unit name is checked +        # against the attribute of the relativedelta instance. +        (relativedelta(days=2, hours=2), 'elephants', 2, '2 days and 2 hours'), + +        # Very high maximum units, but it only ever iterates over +        # each value the relativedelta might have. +        (relativedelta(days=2, hours=2), 'hours', 20, '2 days and 2 hours'), +    ) +) +def test_humanize_delta( +        delta: relativedelta, +        precision: str, +        max_units: int, +        expected: str +): +    assert time.humanize_delta(delta, precision, max_units) == expected + + [email protected]('max_units', (-1, 0)) +def test_humanize_delta_raises_for_invalid_max_units(max_units: int): +    with pytest.raises(ValueError, match='max_units must be positive'): +        time.humanize_delta(relativedelta(days=2, hours=2), 'hours', max_units) + + +    ('stamp', 'expected'), +    ( +        ('Sun, 15 Sep 2019 12:00:00 GMT', datetime(2019, 9, 15, 12, 0, 0, tzinfo=timezone.utc)), +    ) +) +def test_parse_rfc1123(stamp: str, expected: str): +    assert time.parse_rfc1123(stamp) == expected + + +@patch('asyncio.sleep', new_callable=AsyncMock) +def test_wait_until(sleep_patch): +    start = datetime(2019, 1, 1, 0, 0) +    then = datetime(2019, 1, 1, 0, 10) + +    # No return value +    assert asyncio.run(time.wait_until(then, start)) is None + +    sleep_patch.assert_called_once_with(10 * 60) | 
