aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps
diff options
context:
space:
mode:
Diffstat (limited to 'pydis_site/apps')
-rw-r--r--pydis_site/apps/api/migrations/0050_remove_infractions_active_default_value.py18
-rw-r--r--pydis_site/apps/api/models/bot/infraction.py1
-rw-r--r--pydis_site/apps/api/serializers.py2
-rw-r--r--pydis_site/apps/api/tests/test_infractions.py117
-rw-r--r--pydis_site/apps/api/tests/test_reminders.py196
5 files changed, 323 insertions, 11 deletions
diff --git a/pydis_site/apps/api/migrations/0050_remove_infractions_active_default_value.py b/pydis_site/apps/api/migrations/0050_remove_infractions_active_default_value.py
new file mode 100644
index 00000000..90c91d63
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0050_remove_infractions_active_default_value.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.6 on 2020-02-08 19:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0049_deletedmessage_attachments'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='infraction',
+ name='active',
+ field=models.BooleanField(help_text='Whether the infraction is still active.'),
+ ),
+ ]
diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py
index 108fd3a2..f58e89a3 100644
--- a/pydis_site/apps/api/models/bot/infraction.py
+++ b/pydis_site/apps/api/models/bot/infraction.py
@@ -29,7 +29,6 @@ class Infraction(ModelReprMixin, models.Model):
)
)
active = models.BooleanField(
- default=True,
help_text="Whether the infraction is still active."
)
user = models.ForeignKey(
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 0d1a4684..e11c4af2 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -110,7 +110,7 @@ class InfractionSerializer(ModelSerializer):
validators = [
UniqueTogetherValidator(
queryset=Infraction.objects.filter(active=True),
- fields=['user', 'type'],
+ fields=['user', 'type', 'active'],
message='This user already has an active infraction of this type.',
)
]
diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py
index 7a54640e..ca87026c 100644
--- a/pydis_site/apps/api/tests/test_infractions.py
+++ b/pydis_site/apps/api/tests/test_infractions.py
@@ -7,6 +7,7 @@ from django_hosts.resolvers import reverse
from .base import APISubdomainTestCase
from ..models import Infraction, User
+from ..serializers import InfractionSerializer
class UnauthenticatedTests(APISubdomainTestCase):
@@ -54,7 +55,8 @@ class InfractionTests(APISubdomainTestCase):
type='ban',
reason='He terk my jerb!',
hidden=True,
- expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc)
+ expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc),
+ active=True
)
cls.ban_inactive = Infraction.objects.create(
user_id=cls.user.id,
@@ -184,7 +186,8 @@ class CreationTests(APISubdomainTestCase):
'type': 'ban',
'reason': 'He terk my jerb!',
'hidden': True,
- 'expires_at': '5018-11-20T15:52:00+00:00'
+ 'expires_at': '5018-11-20T15:52:00+00:00',
+ 'active': True,
}
response = self.client.post(url, data=data)
@@ -208,7 +211,8 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:infraction-list', host='api')
data = {
'actor': self.user.id,
- 'type': 'kick'
+ 'type': 'kick',
+ 'active': False,
}
response = self.client.post(url, data=data)
@@ -222,7 +226,8 @@ class CreationTests(APISubdomainTestCase):
data = {
'user': 1337,
'actor': self.user.id,
- 'type': 'kick'
+ 'type': 'kick',
+ 'active': True,
}
response = self.client.post(url, data=data)
@@ -236,7 +241,8 @@ class CreationTests(APISubdomainTestCase):
data = {
'user': self.user.id,
'actor': self.user.id,
- 'type': 'hug'
+ 'type': 'hug',
+ 'active': True,
}
response = self.client.post(url, data=data)
@@ -251,7 +257,8 @@ class CreationTests(APISubdomainTestCase):
'user': self.user.id,
'actor': self.user.id,
'type': 'ban',
- 'expires_at': '20/11/5018 15:52:00'
+ 'expires_at': '20/11/5018 15:52:00',
+ 'active': True,
}
response = self.client.post(url, data=data)
@@ -271,7 +278,8 @@ class CreationTests(APISubdomainTestCase):
'user': self.user.id,
'actor': self.user.id,
'type': infraction_type,
- 'expires_at': '5018-11-20T15:52:00+00:00'
+ 'expires_at': '5018-11-20T15:52:00+00:00',
+ 'active': False,
}
response = self.client.post(url, data=data)
@@ -288,7 +296,8 @@ class CreationTests(APISubdomainTestCase):
'user': self.user.id,
'actor': self.user.id,
'type': infraction_type,
- 'hidden': True
+ 'hidden': True,
+ 'active': False,
}
response = self.client.post(url, data=data)
@@ -305,6 +314,7 @@ class CreationTests(APISubdomainTestCase):
'actor': self.user.id,
'type': 'note',
'hidden': False,
+ 'active': False,
}
response = self.client.post(url, data=data)
@@ -494,6 +504,16 @@ class CreationTests(APISubdomainTestCase):
reason="An active ban for the second user"
)
+ def test_integrity_error_if_missing_active_field(self):
+ pattern = 'null value in column "active" violates not-null constraint'
+ with self.assertRaisesRegex(IntegrityError, pattern):
+ Infraction.objects.create(
+ user=self.user,
+ actor=self.user,
+ type='ban',
+ reason='A reason.',
+ )
+
class ExpandedTests(APISubdomainTestCase):
@classmethod
@@ -540,7 +560,8 @@ class ExpandedTests(APISubdomainTestCase):
data = {
'user': self.user.id,
'actor': self.user.id,
- 'type': 'warning'
+ 'type': 'warning',
+ 'active': False
}
response = self.client.post(url, data=data)
@@ -569,3 +590,81 @@ class ExpandedTests(APISubdomainTestCase):
infraction = Infraction.objects.get(id=self.kick.id)
self.assertEqual(infraction.active, data['active'])
self.check_expanded_fields(response.json())
+
+
+class SerializerTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.user = User.objects.create(
+ id=5,
+ name='james',
+ discriminator=1,
+ avatar_hash=None
+ )
+
+ def create_infraction(self, _type: str, active: bool):
+ return Infraction.objects.create(
+ user_id=self.user.id,
+ actor_id=self.user.id,
+ type=_type,
+ reason='A reason.',
+ expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc),
+ active=active
+ )
+
+ def test_is_valid_if_active_infraction_with_same_fields_exists(self):
+ self.create_infraction('ban', active=True)
+ instance = self.create_infraction('ban', active=False)
+
+ data = {'reason': 'hello'}
+ serializer = InfractionSerializer(instance, data=data, partial=True)
+
+ self.assertTrue(serializer.is_valid(), msg=serializer.errors)
+
+ def test_validation_error_if_active_duplicate(self):
+ self.create_infraction('ban', active=True)
+ instance = self.create_infraction('ban', active=False)
+
+ data = {'active': True}
+ serializer = InfractionSerializer(instance, data=data, partial=True)
+
+ if not serializer.is_valid():
+ self.assertIn('non_field_errors', serializer.errors)
+
+ code = serializer.errors['non_field_errors'][0].code
+ msg = f'Expected failure on unique validator but got {serializer.errors}'
+ self.assertEqual(code, 'unique', msg=msg)
+ else: # pragma: no cover
+ self.fail('Validation unexpectedly succeeded.')
+
+ def test_is_valid_for_new_active_infraction(self):
+ self.create_infraction('ban', active=False)
+
+ data = {
+ 'user': self.user.id,
+ 'actor': self.user.id,
+ 'type': 'ban',
+ 'reason': 'A reason.',
+ 'active': True
+ }
+ serializer = InfractionSerializer(data=data)
+
+ self.assertTrue(serializer.is_valid(), msg=serializer.errors)
+
+ def test_validation_error_if_missing_active_field(self):
+ data = {
+ 'user': self.user.id,
+ 'actor': self.user.id,
+ 'type': 'ban',
+ 'reason': 'A reason.',
+ }
+ serializer = InfractionSerializer(data=data)
+
+ if not serializer.is_valid():
+ self.assertIn('active', serializer.errors)
+
+ code = serializer.errors['active'][0].code
+ msg = f'Expected failure on required active field but got {serializer.errors}'
+ self.assertEqual(code, 'required', msg=msg)
+ else: # pragma: no cover
+ self.fail('Validation unexpectedly succeeded.')
diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py
new file mode 100644
index 00000000..3441e0cc
--- /dev/null
+++ b/pydis_site/apps/api/tests/test_reminders.py
@@ -0,0 +1,196 @@
+from datetime import datetime
+
+from django.forms.models import model_to_dict
+from django_hosts.resolvers import reverse
+
+from .base import APISubdomainTestCase
+from ..models import Reminder, User
+
+
+class UnauthedReminderAPITests(APISubdomainTestCase):
+ def setUp(self):
+ super().setUp()
+ self.client.force_authenticate(user=None)
+
+ def test_list_returns_401(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 401)
+
+ def test_create_returns_401(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.post(url, data={'not': 'important'})
+
+ self.assertEqual(response.status_code, 401)
+
+ def test_delete_returns_401(self):
+ url = reverse('bot:reminder-detail', args=('1234',), host='api')
+ response = self.client.delete(url)
+
+ self.assertEqual(response.status_code, 401)
+
+
+class EmptyDatabaseReminderAPITests(APISubdomainTestCase):
+ def test_list_all_returns_empty_list(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), [])
+
+ def test_delete_returns_404(self):
+ url = reverse('bot:reminder-detail', args=('1234',), host='api')
+ response = self.client.delete(url)
+
+ self.assertEqual(response.status_code, 404)
+
+
+class ReminderCreationTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=1234,
+ name='Mermaid Man',
+ discriminator=1234,
+ avatar_hash=None,
+ )
+
+ def test_accepts_valid_data(self):
+ data = {
+ 'author': self.author.id,
+ 'content': 'Remember to...wait what was it again?',
+ 'expiration': datetime.utcnow().isoformat(),
+ 'jump_url': "https://www.google.com",
+ 'channel_id': 123,
+ }
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.post(url, data=data)
+ self.assertEqual(response.status_code, 201)
+ self.assertIsNotNone(Reminder.objects.filter(id=1).first())
+
+ def test_rejects_invalid_data(self):
+ data = {
+ 'author': self.author.id, # Missing multiple required fields
+ }
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.post(url, data=data)
+ self.assertEqual(response.status_code, 400)
+ self.assertRaises(Reminder.DoesNotExist, Reminder.objects.get, id=1)
+
+
+class ReminderDeletionTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=6789,
+ name='Barnacle Boy',
+ discriminator=6789,
+ avatar_hash=None,
+ )
+
+ cls.reminder = Reminder.objects.create(
+ author=cls.author,
+ content="Don't forget to set yourself a reminder",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="https://www.decliningmentalfaculties.com",
+ channel_id=123
+ )
+
+ def test_delete_unknown_reminder_returns_404(self):
+ url = reverse('bot:reminder-detail', args=('something',), host='api')
+ response = self.client.delete(url)
+
+ self.assertEqual(response.status_code, 404)
+
+ def test_delete_known_reminder_returns_204(self):
+ url = reverse('bot:reminder-detail', args=(self.reminder.id,), host='api')
+ response = self.client.delete(url)
+
+ self.assertEqual(response.status_code, 204)
+ self.assertRaises(Reminder.DoesNotExist, Reminder.objects.get, id=self.reminder.id)
+
+
+class ReminderListTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=6789,
+ name='Patrick Star',
+ discriminator=6789,
+ avatar_hash=None,
+ )
+
+ cls.reminder_one = Reminder.objects.create(
+ author=cls.author,
+ content="We should take Bikini Bottom, and push it somewhere else!",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="https://www.icantseemyforehead.com",
+ channel_id=123
+ )
+
+ cls.reminder_two = Reminder.objects.create(
+ author=cls.author,
+ content="Gahhh-I love being purple!",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="https://www.goofygoobersicecreampartyboat.com",
+ channel_id=123,
+ active=False
+ )
+
+ cls.rem_dict_one = model_to_dict(cls.reminder_one)
+ cls.rem_dict_one['expiration'] += 'Z' # Massaging a quirk of the response time format
+ cls.rem_dict_two = model_to_dict(cls.reminder_two)
+ cls.rem_dict_two['expiration'] += 'Z' # Massaging a quirk of the response time format
+
+ def test_reminders_in_full_list(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertCountEqual(response.json(), [self.rem_dict_one, self.rem_dict_two])
+
+ def test_filter_search(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.get(f'{url}?search={self.author.name}')
+
+ self.assertEqual(response.status_code, 200)
+ self.assertCountEqual(response.json(), [self.rem_dict_one, self.rem_dict_two])
+
+ def test_filter_field(self):
+ url = reverse('bot:reminder-list', host='api')
+ response = self.client.get(f'{url}?active=true')
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), [self.rem_dict_one])
+
+
+class ReminderUpdateTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=666,
+ name='Man Ray',
+ discriminator=666,
+ avatar_hash=None,
+ )
+
+ cls.reminder = Reminder.objects.create(
+ author=cls.author,
+ content="Squash those do-gooders",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="https://www.decliningmentalfaculties.com",
+ channel_id=123
+ )
+
+ cls.data = {'content': 'Oops I forgot'}
+
+ def test_patch_updates_record(self):
+ url = reverse('bot:reminder-detail', args=(self.reminder.id,), host='api')
+ response = self.client.patch(url, data=self.data)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(
+ Reminder.objects.filter(id=self.reminder.id).first().content,
+ self.data['content']
+ )