From e3c750a3fdd1beee441c4d62b0c5fe8644255ea6 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 13:00:21 +0200 Subject: Add tests for the AllowList model. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_allowlists.py | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 pydis_site/apps/api/tests/test_allowlists.py (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py new file mode 100644 index 00000000..c6004439 --- /dev/null +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -0,0 +1,104 @@ +from django_hosts.resolvers import reverse + +from .base import APISubdomainTestCase +from ..models import AllowList + +URL = reverse('bot:allowlist-list', host='api') +JPEG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".jpeg", +} +PNG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".png", +} + + +class UnauthenticatedTests(APISubdomainTestCase): + def setUp(self): + super().setUp() + self.client.force_authenticate(user=None) + + def test_cannot_read_allowedlist_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 401) + + +class EmptyDatabaseTests(APISubdomainTestCase): + def test_returns_empty_object(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), []) + + +class FetchTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + + def test_returns_name_in_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) + self.assertEqual(response.json()[1]["content"], self.png_format.content) + + def test_returns_single_item_by_id(self): + response = self.client.get(f'{URL}/{self.jpeg_format.id}') + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json().get("content"), self.jpeg_format.content) + + +class CreationTests(APISubdomainTestCase): + def test_returns_400_for_missing_params(self): + no_type_json = { + "allowed": True, + "content": ".jpeg" + } + no_allowed_json = { + "type": "FILE_FORMAT", + "content": ".jpeg" + } + no_content_json = { + "allowed": True, + "type": "FILE_FORMAT" + } + cases = [{}, no_type_json, no_allowed_json, no_content_json] + + for case in cases: + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) + + def test_returns_201_for_successful_creation(self): + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 201) + + +class DeletionTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + + def test_deleting_unknown_id_returns_404(self): + response = self.client.delete(f"{URL}/200") + + self.assertEqual(response.status_code, 404) + + def test_deleting_known_id_returns_204(self): + response = self.client.delete(f"{URL}/{self.jpeg_format.id}") + + self.assertEqual(response.status_code, 204) + + def test_name_gets_deleted(self): + response = self.client.delete(f"{URL}/{self.png_format.id}") + self.assertEqual(response.status_code, 204) + + response = self.client.get(f"{URL}/{self.png_format.id}") + self.assertNotIn(self.png_format.content, response.json()) -- cgit v1.2.3 From e2aabf684433af3b08079182179fcd964ead8a71 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 20:23:26 +0200 Subject: Fix some broken tests. The test_utils_account.py tests were never running, because the folder they were in had no __init__.py file. The test_models.py file was failing because it had an outdated import of the ModelReprMixin, which has moved to a new file. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_models.py | 4 ++-- pydis_site/tests/__init__.py | 0 pydis_site/tests/test_utils_account.py | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 pydis_site/tests/__init__.py (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index b4754484..e0e347bb 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -3,13 +3,12 @@ from datetime import datetime as dt from django.test import SimpleTestCase from django.utils import timezone -from ..models import ( +from pydis_site.apps.api.models import ( DeletedMessage, DocumentationLink, Infraction, Message, MessageDeletionContext, - ModelReprMixin, Nomination, OffTopicChannelName, OffensiveMessage, @@ -18,6 +17,7 @@ from ..models import ( Tag, User ) +from pydis_site.apps.api.models.mixins import ModelReprMixin class SimpleClass(ModelReprMixin): diff --git a/pydis_site/tests/__init__.py b/pydis_site/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py index e8db7b64..3edca658 100644 --- a/pydis_site/tests/test_utils_account.py +++ b/pydis_site/tests/test_utils_account.py @@ -5,7 +5,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin from django.contrib.auth.models import User from django.contrib.messages.storage.base import BaseStorage from django.http import HttpRequest -from django.test import TestCase +from django.test import RequestFactory, TestCase from pydis_site.apps.api.models import Role, User as DiscordUser from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter @@ -74,6 +74,8 @@ class AccountUtilsTests(TestCase): self.discord_user_two_roles.roles.append(developers_role.id) + self.request_factory = RequestFactory() + def test_account_adapter(self): """Test that our Allauth account adapter functions correctly.""" adapter = AccountAdapter() @@ -86,12 +88,11 @@ class AccountUtilsTests(TestCase): discord_login = SocialLogin(account=self.discord_account) discord_login_role = SocialLogin(account=self.discord_account_role) - discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles) discord_login_not_present = SocialLogin(account=self.discord_account_not_present) github_login = SocialLogin(account=self.github_account) - messages_request = HttpRequest() + messages_request = self.request_factory.get("/") messages_request._messages = BaseStorage(messages_request) with patch("pydis_site.utils.account.reverse") as mock_reverse: @@ -112,8 +113,6 @@ class AccountUtilsTests(TestCase): self.assertEqual(mock_redirect.call_count, 4) self.assertEqual(mock_reverse.call_count, 4) - self.assertTrue(adapter.is_open_for_signup(HttpRequest(), discord_login_two_roles)) - def test_social_account_adapter_populate(self): """Test that our Allauth social account adapter correctly handles data population.""" adapter = SocialAccountAdapter() @@ -126,7 +125,7 @@ class AccountUtilsTests(TestCase): discord_login.account.extra_data["discriminator"] = "0000" user = adapter.populate_user( - HttpRequest(), discord_login, + self.request_factory.get("/"), discord_login, {"username": "user"} ) -- cgit v1.2.3 From 76cd687715e49cee97bac24f5d3c8ca40ffca099 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 20:48:56 +0200 Subject: Rename AllowList to AllowDenyList. https://github.com/python-discord/site/issues/305 --- .../0057_create_new_allowdenylist_model.py | 32 ++++++++++ .../migrations/0057_create_new_allowlist_model.py | 37 ----------- pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/bot/__init__.py | 2 +- pydis_site/apps/api/models/bot/allow_deny_list.py | 37 +++++++++++ pydis_site/apps/api/models/bot/allowlist.py | 39 ------------ pydis_site/apps/api/serializers.py | 8 +-- pydis_site/apps/api/tests/test_allowlists.py | 14 ++--- pydis_site/apps/api/urls.py | 6 +- pydis_site/apps/api/viewsets/__init__.py | 2 +- pydis_site/apps/api/viewsets/bot/__init__.py | 2 +- .../apps/api/viewsets/bot/allow_deny_list.py | 72 ++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/allowlist.py | 72 ---------------------- 13 files changed, 159 insertions(+), 166 deletions(-) create mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py delete mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py create mode 100644 pydis_site/apps/api/models/bot/allow_deny_list.py delete mode 100644 pydis_site/apps/api/models/bot/allowlist.py create mode 100644 pydis_site/apps/api/viewsets/bot/allow_deny_list.py delete mode 100644 pydis_site/apps/api/viewsets/bot/allowlist.py (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py new file mode 100644 index 00000000..c450344b --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py @@ -0,0 +1,32 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='AllowDenyList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField( + choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], + help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AddConstraint( + model_name='allowdenylist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allow_deny_list'), + ) + ] diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py deleted file mode 100644 index 45650d86..00000000 --- a/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='AllowList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField(choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allowlist.')), - ], - options={ - 'abstract': False, - }, - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - migrations.AlterModelTable( - name='allowlist', - table='allow_list', - ), - migrations.AddConstraint( - model_name='allowlist', - constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allowlist'), - ) - ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 2839fbba..34973a8d 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowList, + AllowDenyList, BotSetting, DocumentationLink, DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index b373ee84..1234b35a 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allowlist import AllowList +from .allow_deny_list import AllowDenyList from .bot_setting import BotSetting from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py new file mode 100644 index 00000000..1eef47ba --- /dev/null +++ b/pydis_site/apps/api/models/bot/allow_deny_list.py @@ -0,0 +1,37 @@ +from django.db import models + +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin + + +class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): + """An item that is either allowed or denied.""" + + AllowDenyListType = models.TextChoices( + 'AllowDenyListType', + 'GUILD_INVITE_ID ' + 'FILE_FORMAT ' + 'DOMAIN_NAME ' + 'WORD_WATCHLIST ' + ) + type = models.CharField( + max_length=50, + help_text="The type of allowlist this is on.", + choices=AllowDenyListType.choices, + ) + allowed = models.BooleanField( + help_text="Whether this item is on the allowlist or the denylist." + ) + content = models.TextField( + help_text="The data to add to the allow or denylist." + ) + + class Meta: + """Metaconfig for this model.""" + + # This constraint ensures only one allow or denylist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_allow_deny_list'), + ] diff --git a/pydis_site/apps/api/models/bot/allowlist.py b/pydis_site/apps/api/models/bot/allowlist.py deleted file mode 100644 index fc57ef32..00000000 --- a/pydis_site/apps/api/models/bot/allowlist.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin - - -class AllowList(ModelTimestampMixin, ModelReprMixin, models.Model): - """An item that is either allowed or denied.""" - - AllowListType = models.TextChoices( - 'AllowListType', - 'GUILD_INVITE_ID ' - 'FILE_FORMAT ' - 'DOMAIN_NAME ' - 'WORD_WATCHLIST ' - ) - type = models.CharField( - max_length=50, - help_text="The type of allowlist this is on.", - choices=AllowListType.choices, - ) - allowed = models.BooleanField( - help_text="Whether this item is on the allowlist or the denylist." - ) - content = models.TextField( - help_text="The data to add to the allowlist." - ) - - class Meta: - """Metaconfig for this model.""" - - db_table = 'allow_list' - - # This constraint ensures only one allowlist with the same content - # can exist per type.This means that we cannot have both an allow - # and a deny for the same item, and we cannot have duplicates of the - # same item. - constraints = [ - models.UniqueConstraint(fields=['content', 'type'], name='unique_allowlist'), - ] diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 13030074..d532dd69 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -4,7 +4,7 @@ from rest_framework.validators import UniqueTogetherValidator from rest_framework_bulk import BulkSerializerMixin from .models import ( - AllowList, + AllowDenyList, BotSetting, DeletedMessage, DocumentationLink, @@ -104,13 +104,13 @@ class DocumentationLinkSerializer(ModelSerializer): fields = ('package', 'base_url', 'inventory_url') -class AllowListSerializer(ModelSerializer): - """A class providing (de-)serialization of `AllowList` instances.""" +class AllowDenyListSerializer(ModelSerializer): + """A class providing (de-)serialization of `AllowDenyList` instances.""" class Meta: """Metadata defined for the Django REST Framework.""" - model = AllowList + model = AllowDenyList fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index c6004439..5aa50670 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -1,9 +1,9 @@ from django_hosts.resolvers import reverse -from .base import APISubdomainTestCase -from ..models import AllowList +from pydis_site.apps.api.models import AllowDenyList +from pydis_site.apps.api.tests.base import APISubdomainTestCase -URL = reverse('bot:allowlist-list', host='api') +URL = reverse('bot:allowdenylist-list', host='api') JPEG_ALLOWLIST = { "type": 'FILE_FORMAT', "allowed": True, @@ -38,8 +38,8 @@ class EmptyDatabaseTests(APISubdomainTestCase): class FetchTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): - cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) def test_returns_name_in_list(self): response = self.client.get(URL) @@ -83,8 +83,8 @@ class CreationTests(APISubdomainTestCase): class DeletionTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): - cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) def test_deleting_unknown_id_returns_404(self): response = self.client.delete(f"{URL}/200") diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index bf41f09f..b6ed2914 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( - AllowListViewSet, + AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, @@ -22,8 +22,8 @@ from .viewsets import ( # http://www.django-rest-framework.org/api-guide/routers/#defaultrouter bot_router = DefaultRouter(trailing_slash=False) bot_router.register( - 'allowlists', - AllowListViewSet + 'allow_deny_lists', + AllowDenyListViewSet ) bot_router.register( 'bot-settings', diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index 98d3d586..eb7d5098 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowListViewSet, + AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 86bfc910..11638dd8 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allowlist import AllowListViewSet +from .allow_deny_list import AllowDenyListViewSet from .bot_setting import BotSettingViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py new file mode 100644 index 00000000..a2499d89 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -0,0 +1,72 @@ +from rest_framework.viewsets import ModelViewSet + +from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList +from pydis_site.apps.api.serializers import AllowDenyListSerializer + + +class AllowDenyListViewSet(ModelViewSet): + """ + View providing CRUD operations on items allowed or denied by our bot. + + ## Routes + ### GET /bot/allow_deny_lists + Returns all allow and denylist items in the database. + + #### Response format + >>> [ + ... { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/allow_deny_lists/ + Returns a specific AllowDenyList item from the database. + + #### Response format + >>> { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if the id was not found. + + ### POST /bot/allow_deny_lists + Adds a single AllowDenyList item to the database. + + #### Request body + >>> { + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/allow_deny_lists/ + Deletes the AllowDenyList item with the given `id`. + + #### Status codes + - 204: returned on success + - 404: if a tag with the given `id` does not exist + """ + + serializer_class = AllowDenyListSerializer + queryset = AllowDenyList.objects.all() diff --git a/pydis_site/apps/api/viewsets/bot/allowlist.py b/pydis_site/apps/api/viewsets/bot/allowlist.py deleted file mode 100644 index 7cc82ff7..00000000 --- a/pydis_site/apps/api/viewsets/bot/allowlist.py +++ /dev/null @@ -1,72 +0,0 @@ -from rest_framework.viewsets import ModelViewSet - -from pydis_site.apps.api.models.bot.allowlist import AllowList -from pydis_site.apps.api.serializers import AllowListSerializer - - -class AllowListViewSet(ModelViewSet): - """ - View providing CRUD operations on items whitelisted or blacklisted by our bot. - - ## Routes - ### GET /bot/allowlists - Returns all allowlist items in the database. - - #### Response format - >>> [ - ... { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... }, - ... ... - ... ] - - #### Status codes - - 200: returned on success - - 401: returned if unauthenticated - - ### GET /bot/allowlists/ - Returns a specific AllowList item from the database. - - #### Response format - >>> { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... } - - #### Status codes - - 200: returned on success - - 404: returned if the id was not found. - - ### POST /bot/allowedlists - Adds a single allowedlist item to the database. - - #### Request body - >>> { - ... 'type': str, - ... 'allowed': bool, - ... 'content': str, - ... } - - #### Status codes - - 201: returned on success - - 400: if one of the given fields is invalid - - ### DELETE /bot/allowedlists/ - Deletes the tag with the given `id`. - - #### Status codes - - 204: returned on success - - 404: if a tag with the given `id` does not exist - """ - - serializer_class = AllowListSerializer - queryset = AllowList.objects.all() -- cgit v1.2.3 From 061ace4c1013abbdb5408f682c0c8401d13a59ec Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Fri, 17 Jul 2020 14:59:59 +0200 Subject: Add an endpoint for getting AllowDenyList types. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_allowlists.py | 8 ++++++++ pydis_site/apps/api/viewsets/bot/allow_deny_list.py | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index 5aa50670..1440dc71 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -54,6 +54,14 @@ class FetchTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.json().get("content"), self.jpeg_format.content) + def test_returns_allow_deny_list_types(self): + response = self.client.get(f'{URL}/get_types') + + self.assertEqual(response.status_code, 200) + for api_type, model_type in zip(response.json(), AllowDenyList.AllowDenyListType.choices): + self.assertEquals(api_type[0], model_type[0]) + self.assertEquals(api_type[1], model_type[1]) + class CreationTests(APISubdomainTestCase): def test_returns_400_for_missing_params(self): diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py index a2499d89..2b9fd6a7 100644 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -1,3 +1,6 @@ +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList @@ -70,3 +73,8 @@ class AllowDenyListViewSet(ModelViewSet): serializer_class = AllowDenyListSerializer queryset = AllowDenyList.objects.all() + + @action(detail=False, methods=["get"]) + def get_types(self, _: Request) -> Response: + """Get a list of all the types of AllowDenyLists we support.""" + return Response(AllowDenyList.AllowDenyListType.choices) -- cgit v1.2.3 From 99fc3cde63b4f5aa84d2c1d6b9f5cd9f983fe6cb Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 19 Jul 2020 22:24:10 +0200 Subject: Minor changes to tests, use subTest. --- pydis_site/apps/api/tests/test_allowlists.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index 1440dc71..fd8772d0 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -80,8 +80,9 @@ class CreationTests(APISubdomainTestCase): cases = [{}, no_type_json, no_allowed_json, no_content_json] for case in cases: - response = self.client.post(URL, data=case) - self.assertEqual(response.status_code, 400) + with self.subTest(case=case): + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) def test_returns_201_for_successful_creation(self): response = self.client.post(URL, data=JPEG_ALLOWLIST) @@ -96,17 +97,11 @@ class DeletionTests(APISubdomainTestCase): def test_deleting_unknown_id_returns_404(self): response = self.client.delete(f"{URL}/200") - self.assertEqual(response.status_code, 404) def test_deleting_known_id_returns_204(self): response = self.client.delete(f"{URL}/{self.jpeg_format.id}") - - self.assertEqual(response.status_code, 204) - - def test_name_gets_deleted(self): - response = self.client.delete(f"{URL}/{self.png_format.id}") self.assertEqual(response.status_code, 204) - response = self.client.get(f"{URL}/{self.png_format.id}") + response = self.client.get(f"{URL}/{self.jpeg_format.id}") self.assertNotIn(self.png_format.content, response.json()) -- cgit v1.2.3 From 285c81bc13e996246ad9463951853fc12903d766 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Mon, 27 Jul 2020 09:53:38 +0200 Subject: Rename AllowDenyList to FilterList --- .../0057_create_new_allowdenylist_model.py | 33 ------- .../migrations/0057_create_new_filterlist_model.py | 33 +++++++ pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/bot/__init__.py | 2 +- pydis_site/apps/api/models/bot/allow_deny_list.py | 41 -------- pydis_site/apps/api/models/bot/filter_list.py | 41 ++++++++ pydis_site/apps/api/serializers.py | 10 +- pydis_site/apps/api/tests/test_allowlists.py | 107 --------------------- pydis_site/apps/api/tests/test_filterlists.py | 107 +++++++++++++++++++++ pydis_site/apps/api/urls.py | 7 +- pydis_site/apps/api/viewsets/__init__.py | 2 +- pydis_site/apps/api/viewsets/bot/__init__.py | 2 +- .../apps/api/viewsets/bot/allow_deny_list.py | 97 ------------------- pydis_site/apps/api/viewsets/bot/filter_list.py | 97 +++++++++++++++++++ 14 files changed, 290 insertions(+), 291 deletions(-) delete mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py create mode 100644 pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py delete mode 100644 pydis_site/apps/api/models/bot/allow_deny_list.py create mode 100644 pydis_site/apps/api/models/bot/filter_list.py delete mode 100644 pydis_site/apps/api/tests/test_allowlists.py create mode 100644 pydis_site/apps/api/tests/test_filterlists.py delete mode 100644 pydis_site/apps/api/viewsets/bot/allow_deny_list.py create mode 100644 pydis_site/apps/api/viewsets/bot/filter_list.py (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py deleted file mode 100644 index ab8c5f3f..00000000 --- a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='AllowDenyList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField( - choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), - ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], - help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allow or denylist.')), - ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), - ], - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - migrations.AddConstraint( - model_name='allowdenylist', - constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allow_deny_list'), - ) - ] diff --git a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py new file mode 100644 index 00000000..44025ee8 --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py @@ -0,0 +1,33 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='FilterList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField( + choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], + help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AddConstraint( + model_name='filterlist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list'), + ) + ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 34973a8d..1d0ab7ea 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowDenyList, + FilterList, BotSetting, DocumentationLink, DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 1234b35a..efd98184 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allow_deny_list import AllowDenyList +from .filter_list import FilterList from .bot_setting import BotSetting from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py deleted file mode 100644 index e1e09a78..00000000 --- a/pydis_site/apps/api/models/bot/allow_deny_list.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin - - -class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): - """An item that is either allowed or denied.""" - - AllowDenyListType = models.TextChoices( - 'AllowDenyListType', - 'GUILD_INVITE ' - 'FILE_FORMAT ' - 'DOMAIN_NAME ' - 'FILTER_TOKEN ' - ) - type = models.CharField( - max_length=50, - help_text="The type of allowlist this is on.", - choices=AllowDenyListType.choices, - ) - allowed = models.BooleanField( - help_text="Whether this item is on the allowlist or the denylist." - ) - content = models.TextField( - help_text="The data to add to the allow or denylist." - ) - comment = models.TextField( - help_text="Optional comment on this entry.", - null=True - ) - - class Meta: - """Metaconfig for this model.""" - - # This constraint ensures only one allow or denylist with the - # same content can exist. This means that we cannot have both an allow - # and a deny for the same item, and we cannot have duplicates of the - # same item. - constraints = [ - models.UniqueConstraint(fields=['content', 'type'], name='unique_allow_deny_list'), - ] diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py new file mode 100644 index 00000000..d279e137 --- /dev/null +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -0,0 +1,41 @@ +from django.db import models + +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin + + +class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): + """An item that is either allowed or denied.""" + + FilterListType = models.TextChoices( + 'FilterListType', + 'GUILD_INVITE ' + 'FILE_FORMAT ' + 'DOMAIN_NAME ' + 'FILTER_TOKEN ' + ) + type = models.CharField( + max_length=50, + help_text="The type of allowlist this is on.", + choices=FilterListType.choices, + ) + allowed = models.BooleanField( + help_text="Whether this item is on the allowlist or the denylist." + ) + content = models.TextField( + help_text="The data to add to the allow or denylist." + ) + comment = models.TextField( + help_text="Optional comment on this entry.", + null=True + ) + + class Meta: + """Metaconfig for this model.""" + + # This constraint ensures only one filterlist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'), + ] diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 50f27f1d..8fd40da7 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -4,10 +4,10 @@ from rest_framework.validators import UniqueTogetherValidator from rest_framework_bulk import BulkSerializerMixin from .models import ( - AllowDenyList, BotSetting, DeletedMessage, DocumentationLink, + FilterList, Infraction, LogEntry, MessageDeletionContext, @@ -17,7 +17,7 @@ from .models import ( Reminder, Role, Tag, - User, + User ) @@ -104,13 +104,13 @@ class DocumentationLinkSerializer(ModelSerializer): fields = ('package', 'base_url', 'inventory_url') -class AllowDenyListSerializer(ModelSerializer): - """A class providing (de-)serialization of `AllowDenyList` instances.""" +class FilterListSerializer(ModelSerializer): + """A class providing (de-)serialization of `FilterList` instances.""" class Meta: """Metadata defined for the Django REST Framework.""" - model = AllowDenyList + model = FilterList fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py deleted file mode 100644 index fd8772d0..00000000 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ /dev/null @@ -1,107 +0,0 @@ -from django_hosts.resolvers import reverse - -from pydis_site.apps.api.models import AllowDenyList -from pydis_site.apps.api.tests.base import APISubdomainTestCase - -URL = reverse('bot:allowdenylist-list', host='api') -JPEG_ALLOWLIST = { - "type": 'FILE_FORMAT', - "allowed": True, - "content": ".jpeg", -} -PNG_ALLOWLIST = { - "type": 'FILE_FORMAT', - "allowed": True, - "content": ".png", -} - - -class UnauthenticatedTests(APISubdomainTestCase): - def setUp(self): - super().setUp() - self.client.force_authenticate(user=None) - - def test_cannot_read_allowedlist_list(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 401) - - -class EmptyDatabaseTests(APISubdomainTestCase): - def test_returns_empty_object(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), []) - - -class FetchTests(APISubdomainTestCase): - @classmethod - def setUpTestData(cls): - cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) - - def test_returns_name_in_list(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) - self.assertEqual(response.json()[1]["content"], self.png_format.content) - - def test_returns_single_item_by_id(self): - response = self.client.get(f'{URL}/{self.jpeg_format.id}') - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json().get("content"), self.jpeg_format.content) - - def test_returns_allow_deny_list_types(self): - response = self.client.get(f'{URL}/get_types') - - self.assertEqual(response.status_code, 200) - for api_type, model_type in zip(response.json(), AllowDenyList.AllowDenyListType.choices): - self.assertEquals(api_type[0], model_type[0]) - self.assertEquals(api_type[1], model_type[1]) - - -class CreationTests(APISubdomainTestCase): - def test_returns_400_for_missing_params(self): - no_type_json = { - "allowed": True, - "content": ".jpeg" - } - no_allowed_json = { - "type": "FILE_FORMAT", - "content": ".jpeg" - } - no_content_json = { - "allowed": True, - "type": "FILE_FORMAT" - } - cases = [{}, no_type_json, no_allowed_json, no_content_json] - - for case in cases: - with self.subTest(case=case): - response = self.client.post(URL, data=case) - self.assertEqual(response.status_code, 400) - - def test_returns_201_for_successful_creation(self): - response = self.client.post(URL, data=JPEG_ALLOWLIST) - self.assertEqual(response.status_code, 201) - - -class DeletionTests(APISubdomainTestCase): - @classmethod - def setUpTestData(cls): - cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) - - def test_deleting_unknown_id_returns_404(self): - response = self.client.delete(f"{URL}/200") - self.assertEqual(response.status_code, 404) - - def test_deleting_known_id_returns_204(self): - response = self.client.delete(f"{URL}/{self.jpeg_format.id}") - self.assertEqual(response.status_code, 204) - - response = self.client.get(f"{URL}/{self.jpeg_format.id}") - self.assertNotIn(self.png_format.content, response.json()) diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py new file mode 100644 index 00000000..b577c31c --- /dev/null +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -0,0 +1,107 @@ +from django_hosts.resolvers import reverse + +from pydis_site.apps.api.models import FilterList +from pydis_site.apps.api.tests.base import APISubdomainTestCase + +URL = reverse('bot:filterlist-list', host='api') +JPEG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".jpeg", +} +PNG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".png", +} + + +class UnauthenticatedTests(APISubdomainTestCase): + def setUp(self): + super().setUp() + self.client.force_authenticate(user=None) + + def test_cannot_read_allowedlist_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 401) + + +class EmptyDatabaseTests(APISubdomainTestCase): + def test_returns_empty_object(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), []) + + +class FetchTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) + + def test_returns_name_in_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) + self.assertEqual(response.json()[1]["content"], self.png_format.content) + + def test_returns_single_item_by_id(self): + response = self.client.get(f'{URL}/{self.jpeg_format.id}') + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json().get("content"), self.jpeg_format.content) + + def test_returns_filter_list_types(self): + response = self.client.get(f'{URL}/get-types') + + self.assertEqual(response.status_code, 200) + for api_type, model_type in zip(response.json(), FilterList.FilterListType.choices): + self.assertEquals(api_type[0], model_type[0]) + self.assertEquals(api_type[1], model_type[1]) + + +class CreationTests(APISubdomainTestCase): + def test_returns_400_for_missing_params(self): + no_type_json = { + "allowed": True, + "content": ".jpeg" + } + no_allowed_json = { + "type": "FILE_FORMAT", + "content": ".jpeg" + } + no_content_json = { + "allowed": True, + "type": "FILE_FORMAT" + } + cases = [{}, no_type_json, no_allowed_json, no_content_json] + + for case in cases: + with self.subTest(case=case): + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) + + def test_returns_201_for_successful_creation(self): + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 201) + + +class DeletionTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) + + def test_deleting_unknown_id_returns_404(self): + response = self.client.delete(f"{URL}/200") + self.assertEqual(response.status_code, 404) + + def test_deleting_known_id_returns_204(self): + response = self.client.delete(f"{URL}/{self.jpeg_format.id}") + self.assertEqual(response.status_code, 204) + + response = self.client.get(f"{URL}/{self.jpeg_format.id}") + self.assertNotIn(self.png_format.content, response.json()) diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index b6ed2914..a4fd5b2e 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,10 +3,10 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( - AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, + FilterListViewSet, InfractionViewSet, LogEntryViewSet, NominationViewSet, @@ -18,12 +18,11 @@ from .viewsets import ( UserViewSet ) - # http://www.django-rest-framework.org/api-guide/routers/#defaultrouter bot_router = DefaultRouter(trailing_slash=False) bot_router.register( - 'allow_deny_lists', - AllowDenyListViewSet + 'filter-lists', + FilterListViewSet ) bot_router.register( 'bot-settings', diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index eb7d5098..8699517e 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowDenyListViewSet, + FilterListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 11638dd8..e64e3988 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allow_deny_list import AllowDenyListViewSet +from .filter_list import FilterListViewSet from .bot_setting import BotSettingViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py deleted file mode 100644 index 6ea8da56..00000000 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ /dev/null @@ -1,97 +0,0 @@ -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet - -from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList -from pydis_site.apps.api.serializers import AllowDenyListSerializer - - -class AllowDenyListViewSet(ModelViewSet): - """ - View providing CRUD operations on items allowed or denied by our bot. - - ## Routes - ### GET /bot/allow_deny_lists - Returns all allow and denylist items in the database. - - #### Response format - >>> [ - ... { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... 'comment': "Popular image format.", - ... }, - ... ... - ... ] - - #### Status codes - - 200: returned on success - - 401: returned if unauthenticated - - ### GET /bot/allow_deny_lists/ - Returns a specific AllowDenyList item from the database. - - #### Response format - >>> { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... 'comment': "Popular image format.", - ... } - - #### Status codes - - 200: returned on success - - 404: returned if the id was not found. - - ### GET /bot/allow_deny_lists/get_types - Returns a list of valid list types that can be used in POST requests. - - #### Response format - >>> [ - ... ["GUILD_INVITE","Guild Invite"], - ... ["FILE_FORMAT","File Format"], - ... ["DOMAIN_NAME","Domain Name"], - ... ["FILTER_TOKEN","Filter Token"] - ... ] - - #### Status codes - - 200: returned on success - - ### POST /bot/allow_deny_lists - Adds a single AllowDenyList item to the database. - - #### Request body - >>> { - ... 'type': str, - ... 'allowed': bool, - ... 'content': str, - ... 'comment': Optional[str], - ... } - - #### Status codes - - 201: returned on success - - 400: if one of the given fields is invalid - - ### DELETE /bot/allow_deny_lists/ - Deletes the AllowDenyList item with the given `id`. - - #### Status codes - - 204: returned on success - - 404: if a tag with the given `id` does not exist - """ - - serializer_class = AllowDenyListSerializer - queryset = AllowDenyList.objects.all() - - @action(detail=False, methods=["get"]) - def get_types(self, _: Request) -> Response: - """Get a list of all the types of AllowDenyLists we support.""" - return Response(AllowDenyList.AllowDenyListType.choices) diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py new file mode 100644 index 00000000..2cb21ab9 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/filter_list.py @@ -0,0 +1,97 @@ +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet + +from pydis_site.apps.api.models.bot.filter_list import FilterList +from pydis_site.apps.api.serializers import FilterListSerializer + + +class FilterListViewSet(ModelViewSet): + """ + View providing CRUD operations on items allowed or denied by our bot. + + ## Routes + ### GET /bot/filter-lists + Returns all filterlist items in the database. + + #### Response format + >>> [ + ... { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... 'comment': "Popular image format.", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/filter-lists/ + Returns a specific FilterList item from the database. + + #### Response format + >>> { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... 'comment': "Popular image format.", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if the id was not found. + + ### GET /bot/filter-lists/get-types + Returns a list of valid list types that can be used in POST requests. + + #### Response format + >>> [ + ... ["GUILD_INVITE","Guild Invite"], + ... ["FILE_FORMAT","File Format"], + ... ["DOMAIN_NAME","Domain Name"], + ... ["FILTER_TOKEN","Filter Token"] + ... ] + + #### Status codes + - 200: returned on success + + ### POST /bot/filter-lists + Adds a single FilterList item to the database. + + #### Request body + >>> { + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, + ... 'comment': Optional[str], + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/filter-lists/ + Deletes the FilterList item with the given `id`. + + #### Status codes + - 204: returned on success + - 404: if a tag with the given `id` does not exist + """ + + serializer_class = FilterListSerializer + queryset = FilterList.objects.all() + + @action(detail=False, url_path='get-types', methods=["get"]) + def get_types(self, _: Request) -> Response: + """Get a list of all the types of FilterLists we support.""" + return Response(FilterList.FilterListType.choices) -- cgit v1.2.3 From b4b99c5b56833409c68b89d807c9b9f6d385e7f3 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 29 Jul 2020 14:10:31 +0200 Subject: Add a test for checking duplicates. --- pydis_site/apps/api/tests/test_filterlists.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py index b577c31c..47aaaf31 100644 --- a/pydis_site/apps/api/tests/test_filterlists.py +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -88,6 +88,11 @@ class CreationTests(APISubdomainTestCase): response = self.client.post(URL, data=JPEG_ALLOWLIST) self.assertEqual(response.status_code, 201) + def test_returns_400_for_duplicate_creation(self): + self.client.post(URL, data=JPEG_ALLOWLIST) + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 400) + class DeletionTests(APISubdomainTestCase): @classmethod -- cgit v1.2.3 From de89a3c39c306274a75a41b528fa14155b00f392 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 30 Jul 2020 12:38:31 +0200 Subject: Delete FilterList objects for tests. Now that we have a migration that adds data, we can no longer have tests that operate on the assumption that the database is going to be empty. So, we're now clearing that table before these tests run. --- pydis_site/apps/api/tests/test_filterlists.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'pydis_site/apps/api/tests') diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py index 47aaaf31..188c0fff 100644 --- a/pydis_site/apps/api/tests/test_filterlists.py +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -28,6 +28,10 @@ class UnauthenticatedTests(APISubdomainTestCase): class EmptyDatabaseTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + FilterList.objects.all().delete() + def test_returns_empty_object(self): response = self.client.get(URL) @@ -38,6 +42,7 @@ class EmptyDatabaseTests(APISubdomainTestCase): class FetchTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): + FilterList.objects.all().delete() cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) @@ -64,6 +69,10 @@ class FetchTests(APISubdomainTestCase): class CreationTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + FilterList.objects.all().delete() + def test_returns_400_for_missing_params(self): no_type_json = { "allowed": True, @@ -97,6 +106,7 @@ class CreationTests(APISubdomainTestCase): class DeletionTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): + FilterList.objects.all().delete() cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) -- cgit v1.2.3