diff options
-rw-r--r-- | tests/bot/resources/test_resources.py | 17 | ||||
-rw-r--r-- | tests/bot/rules/test_attachments.py | 52 | ||||
-rw-r--r-- | tests/cogs/__init__.py | 0 | ||||
-rw-r--r-- | tests/cogs/sync/__init__.py | 0 | ||||
-rw-r--r-- | tests/cogs/sync/test_roles.py | 126 | ||||
-rw-r--r-- | tests/cogs/sync/test_users.py | 84 |
6 files changed, 279 insertions, 0 deletions
diff --git a/tests/bot/resources/test_resources.py b/tests/bot/resources/test_resources.py new file mode 100644 index 000000000..73937cfa6 --- /dev/null +++ b/tests/bot/resources/test_resources.py @@ -0,0 +1,17 @@ +import json +import unittest +from pathlib import Path + + +class ResourceValidationTests(unittest.TestCase): + """Validates resources used by the bot.""" + def test_stars_valid(self): + """The resource `bot/resources/stars.json` should contain a list of strings.""" + path = Path('bot', 'resources', 'stars.json') + content = path.read_text() + data = json.loads(content) + + self.assertIsInstance(data, list) + for name in data: + with self.subTest(name=name): + self.assertIsInstance(name, str) diff --git a/tests/bot/rules/test_attachments.py b/tests/bot/rules/test_attachments.py new file mode 100644 index 000000000..4bb0acf7c --- /dev/null +++ b/tests/bot/rules/test_attachments.py @@ -0,0 +1,52 @@ +import asyncio +import unittest +from dataclasses import dataclass +from typing import Any, List + +from bot.rules import attachments + + +# Using `MagicMock` sadly doesn't work for this usecase +# since it's __eq__ compares the MagicMock's ID. We just +# want to compare the actual attributes we set. +@dataclass +class FakeMessage: + author: str + attachments: List[Any] + + +def msg(total_attachments: int) -> FakeMessage: + return FakeMessage(author='lemon', attachments=list(range(total_attachments))) + + +class AttachmentRuleTests(unittest.TestCase): + """Tests applying the `attachment` antispam rule.""" + + def test_allows_messages_without_too_many_attachments(self): + """Messages without too many attachments are allowed as-is.""" + cases = ( + (msg(0), msg(0), msg(0)), + (msg(2), msg(2)), + (msg(0),), + ) + + for last_message, *recent_messages in cases: + with self.subTest(last_message=last_message, recent_messages=recent_messages): + coro = attachments.apply(last_message, recent_messages, {'max': 5}) + self.assertIsNone(asyncio.run(coro)) + + def test_disallows_messages_with_too_many_attachments(self): + """Messages with too many attachments trigger the rule.""" + cases = ( + ((msg(4), msg(0), msg(6)), [msg(4), msg(6)], 10), + ((msg(6),), [msg(6)], 6), + ((msg(1),) * 6, [msg(1)] * 6, 6), + ) + for messages, relevant_messages, total in cases: + with self.subTest(messages=messages, relevant_messages=relevant_messages, total=total): + last_message, *recent_messages = messages + coro = attachments.apply(last_message, recent_messages, {'max': 5}) + self.assertEqual( + asyncio.run(coro), + (f"sent {total} attachments in 5s", ('lemon',), relevant_messages) + ) diff --git a/tests/cogs/__init__.py b/tests/cogs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/cogs/__init__.py diff --git a/tests/cogs/sync/__init__.py b/tests/cogs/sync/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/cogs/sync/__init__.py diff --git a/tests/cogs/sync/test_roles.py b/tests/cogs/sync/test_roles.py new file mode 100644 index 000000000..27ae27639 --- /dev/null +++ b/tests/cogs/sync/test_roles.py @@ -0,0 +1,126 @@ +import unittest + +from bot.cogs.sync.syncers import Role, get_roles_for_sync + + +class GetRolesForSyncTests(unittest.TestCase): + """Tests constructing the roles to synchronize with the site.""" + + def test_get_roles_for_sync_empty_return_for_equal_roles(self): + """No roles should be synced when no diff is found.""" + api_roles = {Role(id=41, name='name', colour=33, permissions=0x8, position=1)} + guild_roles = {Role(id=41, name='name', colour=33, permissions=0x8, position=1)} + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + (set(), set(), set()) + ) + + def test_get_roles_for_sync_returns_roles_to_update_with_non_id_diff(self): + """Roles to be synced are returned when non-ID attributes differ.""" + api_roles = {Role(id=41, name='old name', colour=35, permissions=0x8, position=1)} + guild_roles = {Role(id=41, name='new name', colour=33, permissions=0x8, position=2)} + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + (set(), guild_roles, set()) + ) + + def test_get_roles_only_returns_roles_that_require_update(self): + """Roles that require an update should be returned as the second tuple element.""" + api_roles = { + Role(id=41, name='old name', colour=33, permissions=0x8, position=1), + Role(id=53, name='other role', colour=55, permissions=0, position=3) + } + guild_roles = { + Role(id=41, name='new name', colour=35, permissions=0x8, position=2), + Role(id=53, name='other role', colour=55, permissions=0, position=3) + } + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + ( + set(), + {Role(id=41, name='new name', colour=35, permissions=0x8, position=2)}, + set(), + ) + ) + + def test_get_roles_returns_new_roles_in_first_tuple_element(self): + """Newly created roles are returned as the first tuple element.""" + api_roles = { + Role(id=41, name='name', colour=35, permissions=0x8, position=1), + } + guild_roles = { + Role(id=41, name='name', colour=35, permissions=0x8, position=1), + Role(id=53, name='other role', colour=55, permissions=0, position=2) + } + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + ( + {Role(id=53, name='other role', colour=55, permissions=0, position=2)}, + set(), + set(), + ) + ) + + def test_get_roles_returns_roles_to_update_and_new_roles(self): + """Newly created and updated roles should be returned together.""" + api_roles = { + Role(id=41, name='old name', colour=35, permissions=0x8, position=1), + } + guild_roles = { + Role(id=41, name='new name', colour=40, permissions=0x16, position=2), + Role(id=53, name='other role', colour=55, permissions=0, position=3) + } + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + ( + {Role(id=53, name='other role', colour=55, permissions=0, position=3)}, + {Role(id=41, name='new name', colour=40, permissions=0x16, position=2)}, + set(), + ) + ) + + def test_get_roles_returns_roles_to_delete(self): + """Roles to be deleted should be returned as the third tuple element.""" + api_roles = { + Role(id=41, name='name', colour=35, permissions=0x8, position=1), + Role(id=61, name='to delete', colour=99, permissions=0x9, position=2), + } + guild_roles = { + Role(id=41, name='name', colour=35, permissions=0x8, position=1), + } + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + ( + set(), + set(), + {Role(id=61, name='to delete', colour=99, permissions=0x9, position=2)}, + ) + ) + + def test_get_roles_returns_roles_to_delete_update_and_new_roles(self): + """When roles were added, updated, and removed, all of them are returned properly.""" + api_roles = { + Role(id=41, name='not changed', colour=35, permissions=0x8, position=1), + Role(id=61, name='to delete', colour=99, permissions=0x9, position=2), + Role(id=71, name='to update', colour=99, permissions=0x9, position=3), + } + guild_roles = { + Role(id=41, name='not changed', colour=35, permissions=0x8, position=1), + Role(id=81, name='to create', colour=99, permissions=0x9, position=4), + Role(id=71, name='updated', colour=101, permissions=0x5, position=3), + } + + self.assertEqual( + get_roles_for_sync(guild_roles, api_roles), + ( + {Role(id=81, name='to create', colour=99, permissions=0x9, position=4)}, + {Role(id=71, name='updated', colour=101, permissions=0x5, position=3)}, + {Role(id=61, name='to delete', colour=99, permissions=0x9, position=2)}, + ) + ) diff --git a/tests/cogs/sync/test_users.py b/tests/cogs/sync/test_users.py new file mode 100644 index 000000000..ccaf67490 --- /dev/null +++ b/tests/cogs/sync/test_users.py @@ -0,0 +1,84 @@ +import unittest + +from bot.cogs.sync.syncers import User, get_users_for_sync + + +def fake_user(**kwargs): + kwargs.setdefault('id', 43) + kwargs.setdefault('name', 'bob the test man') + kwargs.setdefault('discriminator', 1337) + kwargs.setdefault('avatar_hash', None) + kwargs.setdefault('roles', (666,)) + kwargs.setdefault('in_guild', True) + return User(**kwargs) + + +class GetUsersForSyncTests(unittest.TestCase): + """Tests constructing the users to synchronize with the site.""" + + def test_get_users_for_sync_returns_nothing_for_empty_params(self): + """When no users are given, none are returned.""" + self.assertEqual( + get_users_for_sync({}, {}), + (set(), set()) + ) + + def test_get_users_for_sync_returns_nothing_for_equal_users(self): + """When no users are updated, none are returned.""" + api_users = {43: fake_user()} + guild_users = {43: fake_user()} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + (set(), set()) + ) + + def test_get_users_for_sync_returns_users_to_update_on_non_id_field_diff(self): + """When a non-ID-field differs, the user to update is returned.""" + api_users = {43: fake_user()} + guild_users = {43: fake_user(name='new fancy name')} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + (set(), {fake_user(name='new fancy name')}) + ) + + def test_get_users_for_sync_returns_users_to_create_with_new_ids_on_guild(self): + """When new users join the guild, they are returned as the first tuple element.""" + api_users = {43: fake_user()} + guild_users = {43: fake_user(), 63: fake_user(id=63)} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + ({fake_user(id=63)}, set()) + ) + + def test_get_users_for_sync_updates_in_guild_field_on_user_leave(self): + """When a user leaves the guild, the `in_guild` flag is updated to `False`.""" + api_users = {43: fake_user(), 63: fake_user(id=63)} + guild_users = {43: fake_user()} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + (set(), {fake_user(id=63, in_guild=False)}) + ) + + def test_get_users_for_sync_updates_and_creates_users_as_needed(self): + """When one user left and another one was updated, both are returned.""" + api_users = {43: fake_user()} + guild_users = {63: fake_user(id=63)} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + ({fake_user(id=63)}, {fake_user(in_guild=False)}) + ) + + def test_get_users_for_sync_does_not_duplicate_update_users(self): + """When the API knows a user the guild doesn't, nothing is performed.""" + api_users = {43: fake_user(in_guild=False)} + guild_users = {} + + self.assertEqual( + get_users_for_sync(guild_users, api_users), + (set(), set()) + ) |