diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/cogs/test_security.py | 54 | ||||
| -rw-r--r-- | tests/cogs/test_token_remover.py | 133 | ||||
| -rw-r--r-- | tests/helpers.py | 10 | ||||
| -rw-r--r-- | tests/test_resources.py | 18 | 
4 files changed, 215 insertions, 0 deletions
| diff --git a/tests/cogs/test_security.py b/tests/cogs/test_security.py new file mode 100644 index 000000000..1efb460fe --- /dev/null +++ b/tests/cogs/test_security.py @@ -0,0 +1,54 @@ +import logging +from unittest.mock import MagicMock + +import pytest +from discord.ext.commands import NoPrivateMessage + +from bot.cogs import security + + +def cog(): +    bot = MagicMock() +    return security.Security(bot) + + +def context(): +    return MagicMock() + + +def test_check_additions(cog): +    cog.bot.check.assert_any_call(cog.check_on_guild) +    cog.bot.check.assert_any_call(cog.check_not_bot) + + +def test_check_not_bot_for_humans(cog, context): +    context.author.bot = False +    assert cog.check_not_bot(context) + + +def test_check_not_bot_for_robots(cog, context): +    context.author.bot = True +    assert not cog.check_not_bot(context) + + +def test_check_on_guild_outside_of_guild(cog, context): +    context.guild = None + +    with pytest.raises(NoPrivateMessage, match="This command cannot be used in private messages."): +        cog.check_on_guild(context) + + +def test_check_on_guild_on_guild(cog, context): +    context.guild = "lemon's lemonade stand" +    assert cog.check_on_guild(context) + + +def test_security_cog_load(caplog): +    bot = MagicMock() +    security.setup(bot) +    bot.add_cog.assert_called_once() +    [record] = caplog.records +    assert record.message == "Cog loaded: Security" +    assert record.levelno == logging.INFO diff --git a/tests/cogs/test_token_remover.py b/tests/cogs/test_token_remover.py new file mode 100644 index 000000000..9d46b3a05 --- /dev/null +++ b/tests/cogs/test_token_remover.py @@ -0,0 +1,133 @@ +import asyncio +from unittest.mock import MagicMock + +import pytest +from discord import Colour + +from bot.cogs.token_remover import ( +    DELETION_MESSAGE_TEMPLATE, +    TokenRemover, +    setup as setup_cog, +) +from bot.constants import Channels, Colours, Event, Icons +from tests.helpers import AsyncMock + + +def token_remover(): +    bot = MagicMock() +    bot.get_cog.return_value = MagicMock() +    bot.get_cog.return_value.send_log_message = AsyncMock() +    return TokenRemover(bot=bot) + + +def message(): +    message = MagicMock() +    message.author.__str__.return_value = 'lemon' +    message.author.bot = False +    message.author.avatar_url_as.return_value = 'picture-lemon.png' +    message.author.id = 42 +    message.author.mention = '@lemon' +    message.channel.send = AsyncMock() +    message.channel.mention = '#lemonade-stand' +    message.content = '' +    message.delete = AsyncMock() +    message.id = 555 +    return message + + +    ('content', 'expected'), +    ( +        ('MTIz', True),  # 123 +        ('YWJj', False),  # abc +    ) +) +def test_is_valid_user_id(content: str, expected: bool): +    assert TokenRemover.is_valid_user_id(content) is expected + + +    ('content', 'expected'), +    ( +        ('DN9r_A', True),  # stolen from dapi, thanks to the author of the 'token' tag! +        ('MTIz', False),  # 123 +    ) +) +def test_is_valid_timestamp(content: str, expected: bool): +    assert TokenRemover.is_valid_timestamp(content) is expected + + +def test_mod_log_property(token_remover): +    token_remover.bot.get_cog.return_value = 'lemon' +    assert token_remover.mod_log == 'lemon' +    token_remover.bot.get_cog.assert_called_once_with('ModLog') + + +def test_ignores_bot_messages(token_remover, message): +    message.author.bot = True +    coroutine = token_remover.on_message(message) +    assert asyncio.run(coroutine) is None + + [email protected]('content', ('', 'lemon wins')) +def test_ignores_messages_without_tokens(token_remover, message, content): +    message.content = content +    coroutine = token_remover.on_message(message) +    assert asyncio.run(coroutine) is None + + [email protected]('content', ('foo.bar.baz', 'x.y.')) +def test_ignores_invalid_tokens(token_remover, message, content): +    message.content = content +    coroutine = token_remover.on_message(message) +    assert asyncio.run(coroutine) is None + + +    'content, censored_token', +    ( +        ('MTIz.DN9R_A.xyz', 'MTIz.DN9R_A.xxx'), +    ) +) +def test_censors_valid_tokens( +    token_remover, message, content, censored_token, caplog +): +    message.content = content +    coroutine = token_remover.on_message(message) +    assert asyncio.run(coroutine) is None  # still no rval + +    # asyncio logs some stuff about its reactor, discard it +    [_, record] = caplog.records +    assert record.message == ( +        "Censored a seemingly valid token sent by lemon (`42`) in #lemonade-stand, " +        f"token was `{censored_token}`" +    ) + +    message.delete.assert_called_once_with() +    message.channel.send.assert_called_once_with( +        DELETION_MESSAGE_TEMPLATE.format(mention='@lemon') +    ) +    token_remover.bot.get_cog.assert_called_with('ModLog') +    message.author.avatar_url_as.assert_called_once_with(static_format='png') + +    mod_log = token_remover.bot.get_cog.return_value +    mod_log.ignore.assert_called_once_with(Event.message_delete, message.id) +    mod_log.send_log_message.assert_called_once_with( +        icon_url=Icons.token_removed, +        colour=Colour(Colours.soft_red), +        title="Token removed!", +        text=record.message, +        thumbnail='picture-lemon.png', +        channel_id=Channels.mod_alerts +    ) + + +def test_setup(caplog): +    bot = MagicMock() +    setup_cog(bot) +    [record] = caplog.records + +    bot.add_cog.assert_called_once() +    assert record.message == "Cog loaded: TokenRemover" diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 000000000..57c6fcc1a --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,10 @@ +from unittest.mock import MagicMock + + +__all__ = ('AsyncMock',) + + +# TODO: Remove me on 3.8 +class AsyncMock(MagicMock): +    async def __call__(self, *args, **kwargs): +        return super(AsyncMock, self).__call__(*args, **kwargs) diff --git a/tests/test_resources.py b/tests/test_resources.py new file mode 100644 index 000000000..2b17aea64 --- /dev/null +++ b/tests/test_resources.py @@ -0,0 +1,18 @@ +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.""" + +    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') | 
