diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 28c0603bd8fbbbf2094b2a9e76a1fb31b1869024..7c730cffef8acc73b747a8590f5510ba43450e08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,11 +17,34 @@ cache: before_script: - python -V # Print out python version for debugging - apt-get -qq update - - apt-get -qq install -y python3-virtualenv python3 python3-dev python3-pip gettext default-libmysqlclient-dev + - apt-get -qq install -y python3-virtualenv python3 python3-dev python3-pip gettext default-mysql-client default-libmysqlclient-dev - export DJANGO_SETTINGS_MODULE=AKPlanning.settings_ci - ./Utils/setup.sh --prod + - mysql --version + +check: + script: + - ./Utils/check.sh --all + +check-migrations: + script: + - source venv/bin/activate + - ./manage.py makemigrations --dry-run --check test: script: - source venv/bin/activate - - python manage.py test --settings AKPlanning.settings_ci --keepdb + - echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql + - pip install pytest-cov unittest-xml-reporting + - coverage run --source='.' manage.py test --settings AKPlanning.settings_ci + after_script: + - source venv/bin/activate + - coverage report + - coverage xml + coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + junit: unit.xml diff --git a/AKDashboard/fixtures/dashboard.json b/AKDashboard/fixtures/dashboard.json new file mode 100644 index 0000000000000000000000000000000000000000..dde1a64d08ef64648bf7e862ef2aa33e15805c0a --- /dev/null +++ b/AKDashboard/fixtures/dashboard.json @@ -0,0 +1,13 @@ +[ +{ + "model": "AKDashboard.dashboardbutton", + "pk": 1, + "fields": { + "text": "Wiki", + "url": "http://wiki.kif.rocks", + "icon": "fab,wikipedia-w", + "color": 2, + "event": 2 + } +} +] diff --git a/AKDashboard/tests.py b/AKDashboard/tests.py index f4fbff87e12c28df6ca244a5cf046d939d5b06cc..9610f5a22ad8f68aea735342f1fd18d034ba5c7b 100644 --- a/AKDashboard/tests.py +++ b/AKDashboard/tests.py @@ -6,6 +6,7 @@ from django.utils.timezone import now from AKDashboard.models import DashboardButton from AKModel.models import Event, AK, AKCategory +from AKModel.tests import BasicViewTests class DashboardTests(TestCase): @@ -64,7 +65,6 @@ class DashboardTests(TestCase): self.event.public = False self.event.save() response = self.client.get(url_dashboard_index) - print(response) self.assertEqual(response.status_code, 200) self.assertFalse(self.event in response.context["events"]) response = self.client.get(url_event_dashboard) @@ -126,3 +126,14 @@ class DashboardTests(TestCase): response = self.client.get(url_event_dashboard) self.assertContains(response, "Dashboard Button Test") + + +class DashboardViewTests(BasicViewTests, TestCase): + fixtures = ['model.json', 'dashboard.json'] + + APP_NAME = 'dashboard' + + VIEWS = [ + ('dashboard', {}), + ('dashboard_event', {'slug': 'kif42'}), + ] diff --git a/AKModel/fixtures/model.json b/AKModel/fixtures/model.json new file mode 100644 index 0000000000000000000000000000000000000000..b5bdcf01084502a30fce82ccb79d90cc83f46ca8 --- /dev/null +++ b/AKModel/fixtures/model.json @@ -0,0 +1,683 @@ +[ +{ + "model": "AKModel.event", + "pk": 1, + "fields": { + "name": "KIF 23", + "slug": "kif23", + "place": "Neuland", + "timezone": "Europe/Berlin", + "start": "2020-10-01T17:41:22Z", + "end": "2020-10-04T17:41:30Z", + "reso_deadline": "2020-10-03T10:00:00Z", + "interest_start": null, + "interest_end": null, + "public": true, + "active": false, + "plan_hidden": true, + "plan_published_at": null, + "base_url": "", + "wiki_export_template_name": "", + "default_slot": "2.00", + "contact_email": "dummy@eyample.org" + } +}, +{ + "model": "AKModel.event", + "pk": 2, + "fields": { + "name": "KIF 42", + "slug": "kif42", + "place": "Neuland noch neuer", + "timezone": "Europe/Berlin", + "start": "2020-11-06T17:51:26Z", + "end": "2020-11-10T17:51:27Z", + "reso_deadline": "2020-11-08T17:51:42Z", + "interest_start": null, + "interest_end": null, + "public": true, + "active": true, + "plan_hidden": false, + "plan_published_at": "2022-12-01T21:50:01Z", + "base_url": "", + "wiki_export_template_name": "", + "default_slot": "2.00", + "contact_email": "dummy@example.org" + } +}, +{ + "model": "AKModel.akowner", + "pk": 1, + "fields": { + "name": "a", + "slug": "a", + "institution": "Uni X", + "link": "", + "event": 2 + } +}, +{ + "model": "AKModel.akowner", + "pk": 2, + "fields": { + "name": "b", + "slug": "b", + "institution": "Hochschule Y", + "link": "", + "event": 2 + } +}, +{ + "model": "AKModel.akowner", + "pk": 3, + "fields": { + "name": "c", + "slug": "c", + "institution": "", + "link": "", + "event": 1 + } +}, +{ + "model": "AKModel.akowner", + "pk": 4, + "fields": { + "name": "d", + "slug": "d", + "institution": "", + "link": "", + "event": 1 + } +}, +{ + "model": "AKModel.akcategory", + "pk": 1, + "fields": { + "name": "Spaâ–€", + "color": "275246", + "description": "", + "present_by_default": true, + "event": 1 + } +}, +{ + "model": "AKModel.akcategory", + "pk": 2, + "fields": { + "name": "Ernst", + "color": "234567", + "description": "", + "present_by_default": true, + "event": 1 + } +}, +{ + "model": "AKModel.akcategory", + "pk": 3, + "fields": { + "name": "Spaâ–€/Kultur", + "color": "333333", + "description": "", + "present_by_default": true, + "event": 2 + } +}, +{ + "model": "AKModel.akcategory", + "pk": 4, + "fields": { + "name": "Inhalt", + "color": "456789", + "description": "", + "present_by_default": true, + "event": 2 + } +}, +{ + "model": "AKModel.akcategory", + "pk": 5, + "fields": { + "name": "Meta", + "color": "111111", + "description": "", + "present_by_default": true, + "event": 2 + } +}, +{ + "model": "AKModel.aktrack", + "pk": 1, + "fields": { + "name": "Track 1", + "color": "", + "event": 2 + } +}, +{ + "model": "AKModel.aktag", + "pk": 1, + "fields": { + "name": "metametameta" + } +}, +{ + "model": "AKModel.akrequirement", + "pk": 1, + "fields": { + "name": "Beamer", + "event": 1 + } +}, +{ + "model": "AKModel.akrequirement", + "pk": 2, + "fields": { + "name": "Internet", + "event": 1 + } +}, +{ + "model": "AKModel.akrequirement", + "pk": 3, + "fields": { + "name": "BBB", + "event": 2 + } +}, +{ + "model": "AKModel.akrequirement", + "pk": 4, + "fields": { + "name": "Mumble", + "event": 2 + } +}, +{ + "model": "AKModel.akrequirement", + "pk": 5, + "fields": { + "name": "Matrix", + "event": 2 + } +}, +{ + "model": "AKModel.historicalak", + "pk": 1, + "fields": { + "id": 1, + "name": "Test AK Inhalt", + "short_name": "test1", + "description": "", + "link": "", + "protocol_link": "", + "reso": false, + "present": true, + "notes": "", + "interest": -1, + "category": 4, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:28:59.265Z", + "history_change_reason": null, + "history_type": "+", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 2, + "fields": { + "id": 1, + "name": "Test AK Inhalt", + "short_name": "test1", + "description": "", + "link": "", + "protocol_link": "", + "reso": false, + "present": true, + "notes": "", + "interest": -1, + "category": 4, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:28:59.305Z", + "history_change_reason": null, + "history_type": "~", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 3, + "fields": { + "id": 2, + "name": "Test AK Meta", + "short_name": "test2", + "description": "", + "link": "", + "protocol_link": "", + "reso": false, + "present": null, + "notes": "", + "interest": -1, + "category": 5, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:29:40.296Z", + "history_change_reason": null, + "history_type": "+", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 4, + "fields": { + "id": 2, + "name": "Test AK Meta", + "short_name": "test2", + "description": "", + "link": "", + "protocol_link": "", + "reso": false, + "present": null, + "notes": "", + "interest": -1, + "category": 5, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:29:40.355Z", + "history_change_reason": null, + "history_type": "~", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 5, + "fields": { + "id": 3, + "name": "AK Wish", + "short_name": "wish1", + "description": "Description of my Wish", + "link": "", + "protocol_link": "", + "reso": false, + "present": null, + "notes": "We need to find a volunteer first...", + "interest": -1, + "category": 3, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:30:59.469Z", + "history_change_reason": null, + "history_type": "+", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 6, + "fields": { + "id": 3, + "name": "AK Wish", + "short_name": "wish1", + "description": "Description of my Wish", + "link": "", + "protocol_link": "", + "reso": false, + "present": null, + "notes": "We need to find a volunteer first...", + "interest": -1, + "category": 3, + "track": null, + "event": 2, + "history_date": "2021-05-04T10:30:59.509Z", + "history_change_reason": null, + "history_type": "~", + "history_user": null + } +}, +{ + "model": "AKModel.historicalak", + "pk": 7, + "fields": { + "id": 2, + "name": "Test AK Meta", + "short_name": "test2", + "description": "", + "link": "", + "protocol_link": "", + "reso": false, + "present": null, + "notes": "", + "interest": -1, + "category": 5, + "track": 1, + "event": 2, + "history_date": "2022-12-02T12:27:14.277Z", + "history_change_reason": null, + "history_type": "~", + "history_user": null + } +}, +{ + "model": "AKModel.ak", + "pk": 1, + "fields": { + "name": "Test AK Inhalt", + "short_name": "test1", + "description": "", + "link": "", + "protocol_link": "", + "category": 4, + "track": null, + "reso": false, + "present": true, + "notes": "", + "interest": -1, + "interest_counter": 0, + "event": 2, + "owners": [ + 1 + ], + "tags": [], + "requirements": [ + 3 + ], + "conflicts": [], + "prerequisites": [] + } +}, +{ + "model": "AKModel.ak", + "pk": 2, + "fields": { + "name": "Test AK Meta", + "short_name": "test2", + "description": "", + "link": "", + "protocol_link": "", + "category": 5, + "track": 1, + "reso": false, + "present": null, + "notes": "", + "interest": -1, + "interest_counter": 0, + "event": 2, + "owners": [ + 2 + ], + "tags": [ + 1 + ], + "requirements": [], + "conflicts": [], + "prerequisites": [] + } +}, +{ + "model": "AKModel.ak", + "pk": 3, + "fields": { + "name": "AK Wish", + "short_name": "wish1", + "description": "Description of my Wish", + "link": "", + "protocol_link": "", + "category": 3, + "track": null, + "reso": false, + "present": null, + "notes": "We need to find a volunteer first...", + "interest": -1, + "interest_counter": 0, + "event": 2, + "owners": [], + "tags": [], + "requirements": [ + 4 + ], + "conflicts": [ + 1 + ], + "prerequisites": [ + 2 + ] + } +}, +{ + "model": "AKModel.room", + "pk": 1, + "fields": { + "name": "Testraum", + "location": "BBB", + "capacity": -1, + "event": 2, + "properties": [ + 3 + ] + } +}, +{ + "model": "AKModel.room", + "pk": 2, + "fields": { + "name": "Testraum 2", + "location": "", + "capacity": 30, + "event": 2, + "properties": [] + } +}, +{ + "model": "AKModel.akslot", + "pk": 1, + "fields": { + "ak": 1, + "room": 2, + "start": "2020-11-06T18:30:00Z", + "duration": "2.00", + "fixed": false, + "event": 2, + "updated": "2022-12-02T12:23:11.856Z" + } +}, +{ + "model": "AKModel.akslot", + "pk": 2, + "fields": { + "ak": 2, + "room": 1, + "start": "2020-11-08T12:30:00Z", + "duration": "2.00", + "fixed": false, + "event": 2, + "updated": "2022-12-02T12:23:18.279Z" + } +}, +{ + "model": "AKModel.akslot", + "pk": 3, + "fields": { + "ak": 3, + "room": null, + "start": null, + "duration": "1.50", + "fixed": false, + "event": 2, + "updated": "2021-05-04T10:30:59.523Z" + } +}, +{ + "model": "AKModel.akslot", + "pk": 4, + "fields": { + "ak": 3, + "room": null, + "start": null, + "duration": "3.00", + "fixed": false, + "event": 2, + "updated": "2021-05-04T10:30:59.528Z" + } +}, +{ + "model": "AKModel.akslot", + "pk": 5, + "fields": { + "ak": 1, + "room": null, + "start": null, + "duration": "2.00", + "fixed": false, + "event": 2, + "updated": "2022-12-02T12:23:11.856Z" + } +}, +{ + "model": "AKModel.constraintviolation", + "pk": 1, + "fields": { + "type": "soa", + "level": 10, + "event": 2, + "ak_owner": null, + "room": null, + "requirement": null, + "category": null, + "comment": "", + "timestamp": "2022-12-02T12:23:11.875Z", + "manually_resolved": false, + "aks": [ + 1 + ], + "ak_slots": [ + 1 + ] + } +}, +{ + "model": "AKModel.constraintviolation", + "pk": 2, + "fields": { + "type": "rng", + "level": 10, + "event": 2, + "ak_owner": null, + "room": 2, + "requirement": 3, + "category": null, + "comment": "", + "timestamp": "2022-12-02T12:23:11.890Z", + "manually_resolved": false, + "aks": [ + 1 + ], + "ak_slots": [ + 1 + ] + } +}, +{ + "model": "AKModel.constraintviolation", + "pk": 3, + "fields": { + "type": "soa", + "level": 10, + "event": 2, + "ak_owner": null, + "room": null, + "requirement": null, + "category": null, + "comment": "", + "timestamp": "2022-12-02T12:23:18.301Z", + "manually_resolved": false, + "aks": [ + 2 + ], + "ak_slots": [ + 2 + ] + } +}, +{ + "model": "AKModel.availability", + "pk": 1, + "fields": { + "event": 2, + "person": null, + "room": null, + "ak": 1, + "ak_category": null, + "start": "2020-11-07T08:00:00Z", + "end": "2020-11-07T18:00:00Z" + } +}, +{ + "model": "AKModel.availability", + "pk": 2, + "fields": { + "event": 2, + "person": null, + "room": null, + "ak": 1, + "ak_category": null, + "start": "2020-11-09T10:00:00Z", + "end": "2020-11-09T16:30:00Z" + } +}, +{ + "model": "AKModel.availability", + "pk": 3, + "fields": { + "event": 2, + "person": null, + "room": 1, + "ak": null, + "ak_category": null, + "start": "2020-11-06T17:51:26Z", + "end": "2020-11-10T23:00:00Z" + } +}, +{ + "model": "AKModel.availability", + "pk": 4, + "fields": { + "event": 2, + "person": null, + "room": 2, + "ak": null, + "ak_category": null, + "start": "2020-11-06T17:51:26Z", + "end": "2020-11-06T21:00:00Z" + } +}, +{ + "model": "AKModel.availability", + "pk": 5, + "fields": { + "event": 2, + "person": null, + "room": 2, + "ak": null, + "ak_category": null, + "start": "2020-11-08T15:30:00Z", + "end": "2020-11-08T19:30:00Z" + } +}, +{ + "model": "AKModel.availability", + "pk": 6, + "fields": { + "event": 2, + "person": null, + "room": 2, + "ak": null, + "ak_category": null, + "start": "2020-11-07T18:30:00Z", + "end": "2020-11-07T21:30:00Z" + } +} +] diff --git a/AKModel/tests.py b/AKModel/tests.py index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..106de0979c99432f1589ab5be16909fc8d70247d 100644 --- a/AKModel/tests.py +++ b/AKModel/tests.py @@ -1 +1,112 @@ -# Create your tests here. +from typing import List + +from django.contrib.auth.models import User +from django.contrib.messages import get_messages +from django.contrib.messages.storage.base import Message +from django.test import TestCase +from django.urls import reverse_lazy + +from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, Room, AKSlot, AKOrgaMessage, \ + ConstraintViolation, DefaultSlot + + +class BasicViewTests: + VIEWS = [] + APP_NAME = '' + VIEWS_STAFF_ONLY = [] + + def setUp(self): + self.staff_user = User.objects.create( + username='Test Staff User', email='teststaff@example.com', password='staffpw', + is_staff=True, is_active=True + ) + self.admin_user = User.objects.create( + username='Test Admin User', email='testadmin@example.com', password='adminpw', + is_staff=True, is_superuser=True, is_active=True + ) + self.deactivated_user = User.objects.create( + username='Test Deactivated User', email='testdeactivated@example.com', password='deactivatedpw', + is_staff=True, is_active=False + ) + + def _name_and_url(self, view_name): + """ + Get full view name (with prefix if there is one) and url from raw view definition + + :param view_name: raw definition of a view + :type view_name: (str, dict) + :return: full view name with prefix if applicable, url of the view + :rtype: str, str + """ + view_name_with_prefix = f"{self.APP_NAME}:{view_name[0]}" if self.APP_NAME != "" else view_name[0] + url = reverse_lazy(view_name_with_prefix, kwargs=view_name[1]) + return view_name_with_prefix, url + + def _assert_message(self, response, expected_message, msg_prefix=""): + messages:List[Message] = list(get_messages(response.wsgi_request)) + + msg_count = "No message shown to user" + msg_content = "Wrong message, expected '{expected_message}'" + if msg_prefix != "": + msg_count = f"{msg_prefix}: {msg_count}" + msg_content = f"{msg_prefix}: {msg_content}" + + # Check that the last message correctly reports the issue + # (there might be more messages from previous calls that were not already rendered) + self.assertGreater(len(messages), 0, msg=msg_count) + self.assertEqual(messages[-1].message, expected_message, msg=msg_content) + + def test_views_for_200(self): + for view_name in self.VIEWS: + view_name_with_prefix, url = self._name_and_url(view_name) + response = self.client.get(url) + self.assertEqual(response.status_code, 200, msg=f"{view_name_with_prefix} ({url}) broken") + + def test_access_control_staff_only(self): + self.client.logout() + for view_name in self.VIEWS_STAFF_ONLY: + view_name_with_prefix, url = self._name_and_url(view_name) + response = self.client.get(url) + self.assertEqual(response.status_code, 302, msg=f"{view_name_with_prefix} ({url}) accessible by non-staff") + + self.client.force_login(self.staff_user) + for view_name in self.VIEWS_STAFF_ONLY: + view_name_with_prefix, url = self._name_and_url(view_name) + response = self.client.get(url) + self.assertEqual(response.status_code, 200, + msg=f"{view_name_with_prefix} ({url}) should be accessible for staff (but isn't)") + + self.client.force_login(self.deactivated_user) + for view_name in self.VIEWS_STAFF_ONLY: + view_name_with_prefix, url = self._name_and_url(view_name) + response = self.client.get(url) + self.assertEqual(response.status_code, 302, + msg=f"{view_name_with_prefix} ({url}) still accessible for deactivated user") + + +class ModelViewTests(BasicViewTests, TestCase): + fixtures = ['model.json'] + + ADMIN_MODELS = [ + (Event, 'event'), (AKOwner, 'akowner'), (AKCategory, 'akcategory'), (AKTrack, 'aktrack'), + (AKRequirement, 'akrequirement'), (AK, 'ak'), (Room, 'room'), (AKSlot, 'akslot'), + (AKOrgaMessage, 'akorgamessage'), (ConstraintViolation, 'constraintviolation'), + (DefaultSlot, 'defaultslot') + ] + + def test_admin(self): + self.client.force_login(self.admin_user) + + for model in self.ADMIN_MODELS: + if model[1] == "event": + continue + view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_add', {})) + response = self.client.get(url) + self.assertEqual(response.status_code, 200, msg=f"Add form for model {model[1]} ({url}) broken") + + for model in self.ADMIN_MODELS: + m = model[0].objects.first() + if m is not None: + view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_change', {'object_id': m.pk})) + response = self.client.get(url) + self.assertEqual(response.status_code, 200, msg=f"Edit form for model {model[1]} ({url}) broken") diff --git a/AKPlan/tests.py b/AKPlan/tests.py index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..13f671c6a965e3ccaca873019c6ef330c8efaff1 100644 --- a/AKPlan/tests.py +++ b/AKPlan/tests.py @@ -1 +1,28 @@ -# Create your tests here. +from django.test import TestCase + +from AKModel.tests import BasicViewTests + + +class PlanViewTests(BasicViewTests, TestCase): + fixtures = ['model.json'] + APP_NAME = 'plan' + + VIEWS = [ + ('plan_overview', {'event_slug': 'kif42'}), + ('plan_wall', {'event_slug': 'kif42'}), + ('plan_room', {'event_slug': 'kif42', 'pk': 2}), + ('plan_track', {'event_slug': 'kif42', 'pk': 1}), + ] + + def test_plan_hidden(self): + view_name_with_prefix, url = self._name_and_url(('plan_overview', {'event_slug': 'kif23'})) + + self.client.logout() + response = self.client.get(url) + self.assertContains(response, "Plan is not visible (yet).", + msg_prefix="Plan is visible even though it shouldn't be") + + self.client.force_login(self.staff_user) + response = self.client.get(url) + self.assertNotContains(response, "Plan is not visible (yet).", + msg_prefix="Plan is not visible for staff user") diff --git a/AKPlanning/settings_ci.py b/AKPlanning/settings_ci.py index 99aa3a7a644abeb993403e35a65b74cdfa87a989..c8e6a62e272c2d8985707e25537400983271f2a0 100644 --- a/AKPlanning/settings_ci.py +++ b/AKPlanning/settings_ci.py @@ -16,10 +16,14 @@ DATABASES = { 'PASSWORD': 'mysql', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", - 'charset': "utf8mb4", }, 'TEST': { - 'NAME': 'test', + 'NAME': 'tests', + 'CHARSET': "utf8mb4", + 'COLLATION': 'utf8mb4_unicode_ci', }, } } + +TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' +TEST_OUTPUT_FILE_NAME = 'unit.xml' diff --git a/AKScheduling/tests.py b/AKScheduling/tests.py index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..8b7bcf91eeb40abe1185b814b0018c2f36715c51 100644 --- a/AKScheduling/tests.py +++ b/AKScheduling/tests.py @@ -1 +1,17 @@ -# Create your tests here. +from django.test import TestCase +from AKModel.tests import BasicViewTests + + +class ModelViewTests(BasicViewTests, TestCase): + fixtures = ['model.json'] + + VIEWS_STAFF_ONLY = [ + ('admin:schedule', {'event_slug': 'kif42'}), + ('admin:slots_unscheduled', {'event_slug': 'kif42'}), + ('admin:constraint-violations', {'slug': 'kif42'}), + ('admin:special-attention', {'slug': 'kif42'}), + ('admin:cleanup-wish-slots', {'event_slug': 'kif42'}), + ('admin:autocreate-availabilities', {'event_slug': 'kif42'}), + ('admin:tracks_manage', {'event_slug': 'kif42'}), + ('admin:enter-interest', {'event_slug': 'kif42', 'pk': 1}), + ] diff --git a/AKSubmission/api.py b/AKSubmission/api.py index 29a6f42a9f371b53aef98ead6a4d7b2b4d6e20f3..db46d40c0455c0553bb311302cc39145c41af1f7 100644 --- a/AKSubmission/api.py +++ b/AKSubmission/api.py @@ -26,8 +26,8 @@ def increment_interest_counter(request, event_slug, pk, **kwargs): """ Increment interest counter for AK """ - ak = AK.objects.get(pk=pk) - if ak: + try: + ak = AK.objects.get(pk=pk) # Check whether interest indication is currently allowed current_timestamp = datetime.now().astimezone(ak.event.timezone) if ak_interest_indication_active(ak.event, current_timestamp): @@ -35,4 +35,5 @@ def increment_interest_counter(request, event_slug, pk, **kwargs): ak.save() return Response({'interest_counter': ak.interest_counter}, status=status.HTTP_200_OK) return Response(status=status.HTTP_403_FORBIDDEN) - return Response(status=status.HTTP_404_NOT_FOUND) + except AK.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) diff --git a/AKSubmission/tests.py b/AKSubmission/tests.py index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..4cff0830106ccbffcd142edf2940d994257e3148 100644 --- a/AKSubmission/tests.py +++ b/AKSubmission/tests.py @@ -1 +1,162 @@ -# Create your tests here. +from datetime import timedelta + +from django.test import TestCase +from django.urls import reverse_lazy +from django.utils.datetime_safe import datetime + +from AKModel.models import AK, AKSlot, Event +from AKModel.tests import BasicViewTests + + +class ModelViewTests(BasicViewTests, TestCase): + fixtures = ['model.json'] + + VIEWS = [ + ('submission_overview', {'event_slug': 'kif42'}), + ('ak_detail', {'event_slug': 'kif42', 'pk': 1}), + ('ak_history', {'event_slug': 'kif42', 'pk': 1}), + ('ak_edit', {'event_slug': 'kif42', 'pk': 1}), + ('akslot_add', {'event_slug': 'kif42', 'pk': 1}), + ('akmessage_add', {'event_slug': 'kif42', 'pk': 1}), + ('akslot_edit', {'event_slug': 'kif42', 'pk': 5}), + ('akslot_delete', {'event_slug': 'kif42', 'pk': 5}), + ('ak_list', {'event_slug': 'kif42'}), + ('ak_list_by_category', {'event_slug': 'kif42', 'category_pk': 4}), + ('ak_list_by_track', {'event_slug': 'kif42', 'track_pk': 1}), + ('akowner_create', {'event_slug': 'kif42'}), + ('akowner_edit', {'event_slug': 'kif42', 'slug': 'a'}), + ('submit_ak', {'event_slug': 'kif42', 'owner_slug': 'a'}), + ('submit_ak_wish', {'event_slug': 'kif42'}), + ('error_not_configured', {'event_slug': 'kif42'}), + ] + + APP_NAME = 'submit' + + def test_akslot_edit_delete_prevention(self): + """ + Slots planned already may not be modified or deleted in front end + """ + self.client.logout() + + view_name_with_prefix, url = self._name_and_url(('akslot_edit', {'event_slug': 'kif42', 'pk': 1})) + response = self.client.get(url) + self.assertEqual(response.status_code, 302, + msg=f"AK Slot editing ({url}) possible even though slot was already scheduled") + self._assert_message(response, "You cannot edit a slot that has already been scheduled") + + view_name_with_prefix, url = self._name_and_url(('akslot_delete', {'event_slug': 'kif42', 'pk': 1})) + response = self.client.get(url) + self.assertEqual(response.status_code, 302, + msg=f"AK Slot deletion ({url}) possible even though slot was already scheduled") + self._assert_message(response, "You cannot delete a slot that has already been scheduled") + + def test_slot_creation_deletion(self): + ak_args = {'event_slug': 'kif42', 'pk': 1} + redirect_url = reverse_lazy(f"{self.APP_NAME}:ak_detail", kwargs=ak_args) + + count_slots = AK.objects.get(pk=1).akslot_set.count() + + create_url = reverse_lazy(f"{self.APP_NAME}:akslot_add", kwargs=ak_args) + response = self.client.post(create_url, {'ak': 1, 'event': 2, 'duration': 1.5}) + self.assertRedirects(response, redirect_url, status_code=302, target_status_code=200, + msg_prefix="Did not correctly trigger redirect") + self.assertEqual(AK.objects.get(pk=1).akslot_set.count(), count_slots + 1, + msg="New slot was not correctly saved") + + # Get primary key of newly created Slot + slot_pk = AK.objects.get(pk=1).akslot_set.order_by('pk').last().pk + + edit_url = reverse_lazy(f"{self.APP_NAME}:akslot_edit", kwargs={'event_slug': 'kif42', 'pk': slot_pk}) + response = self.client.get(edit_url) + self.assertEqual(response.status_code, 200, msg=f"Cant open edit view for newly created slot ({edit_url})") + response = self.client.post(edit_url, {'ak': 1, 'event': 2, 'duration': 2}) + self.assertRedirects(response, redirect_url, status_code=302, target_status_code=200, + msg_prefix="Did not correctly trigger redirect") + self.assertEqual(AKSlot.objects.get(pk=slot_pk).duration, 2, + msg="Slot was not correctly changed") + + deletion_url = reverse_lazy(f"{self.APP_NAME}:akslot_delete", kwargs={'event_slug': 'kif42', 'pk': slot_pk}) + response = self.client.get(deletion_url) + self.assertEqual(response.status_code, 200, + msg="Cant open deletion view for newly created slot ({deletion_url})") + response = self.client.post(deletion_url, {}) + self.assertRedirects(response, redirect_url, status_code=302, target_status_code=200, + msg_prefix="Did not correctly trigger redirect") + self.assertFalse(AKSlot.objects.filter(pk=slot_pk).exists(), msg="Slot was not correctly deleted") + self.assertEqual(AK.objects.get(pk=1).akslot_set.count(), count_slots, msg="AK still has to many slots") + + def test_ak_owner_editing(self): + # Test editing of new user + edit_url = reverse_lazy(f"{self.APP_NAME}:akowner_edit_dispatch", kwargs={'event_slug': 'kif42'}) + + base_url = reverse_lazy(f"{self.APP_NAME}:submission_overview", kwargs={'event_slug': 'kif42'}) + response = self.client.post(edit_url, {'owner_id': -1}) + self.assertRedirects(response, base_url, status_code=302, target_status_code=200, + msg_prefix="Did not redirect to start page even though no user was selected") + self._assert_message(response, "No user selected") + + edit_redirect_url = reverse_lazy(f"{self.APP_NAME}:akowner_edit", kwargs={'event_slug': 'kif42', 'slug': 'a'}) + response = self.client.post(edit_url, {'owner_id': 1}) + self.assertRedirects(response, edit_redirect_url, status_code=302, target_status_code=200, + msg_prefix=f"Dispatch redirect failed (should go to {edit_redirect_url})") + + def test_ak_owner_selection(self): + select_url = reverse_lazy(f"{self.APP_NAME}:akowner_select", kwargs={'event_slug': 'kif42'}) + + create_url = reverse_lazy(f"{self.APP_NAME}:akowner_create", kwargs={'event_slug': 'kif42'}) + response = self.client.post(select_url, {'owner_id': -1}) + self.assertRedirects(response, create_url, status_code=302, target_status_code=200, + msg_prefix="Did not redirect to user create view even though no user was specified") + + add_redirect_url = reverse_lazy(f"{self.APP_NAME}:submit_ak", kwargs={'event_slug': 'kif42', 'owner_slug': 'a'}) + response = self.client.post(select_url, {'owner_id': 1}) + self.assertRedirects(response, add_redirect_url, status_code=302, target_status_code=200, + msg_prefix=f"Dispatch redirect to ak submission page failed (should go to {add_redirect_url})") + + def test_orga_message_submission(self): + form_url = reverse_lazy(f"{self.APP_NAME}:akmessage_add", kwargs={'event_slug': 'kif42', 'pk': 1}) + detail_url = reverse_lazy(f"{self.APP_NAME}:ak_detail", kwargs={'event_slug': 'kif42', 'pk': 1}) + + count_messages = AK.objects.get(pk=1).akorgamessage_set.count() + + response = self.client.get(form_url) + self.assertEqual(response.status_code, 200, msg="Could not load message form view") + response = self.client.post(form_url, {'ak': 1, 'event': 2, 'text': 'Test message text'}) + self.assertRedirects(response, detail_url, status_code=302, target_status_code=200, + msg_prefix=f"Did not trigger redirect to ak detail page ({detail_url})") + self._assert_message(response, "Message to organizers successfully saved") + self.assertEqual(AK.objects.get(pk=1).akorgamessage_set.count(), count_messages + 1, + msg="Message was not correctly saved") + + def test_interest_api(self): + interest_api_url = "/kif42/api/ak/1/indicate-interest/" + + ak = AK.objects.get(pk=1) + event = Event.objects.get(slug='kif42') + ak_interest_counter = ak.interest_counter + + response = self.client.get(interest_api_url) + self.assertEqual(response.status_code, 405, "Should not be accessible via GET") + + event.interest_start = datetime.now().astimezone(event.timezone) + timedelta(minutes=-10) + event.interest_end = datetime.now().astimezone(event.timezone) + timedelta(minutes=+10) + event.save() + + response = self.client.post(interest_api_url) + self.assertEqual(response.status_code, 200, f"API end point not working ({interest_api_url})") + self.assertEqual(AK.objects.get(pk=1).interest_counter, ak_interest_counter + 1, "Counter was not increased") + + event.interest_end = datetime.now().astimezone(event.timezone) + timedelta(minutes=-2) + event.save() + + response = self.client.post(interest_api_url) + self.assertEqual(response.status_code, 403, + "API end point still reachable even though interest indication window ended ({interest_api_url})") + self.assertEqual(AK.objects.get(pk=1).interest_counter, ak_interest_counter + 1, + "Counter was increased even though interest indication window ended") + + invalid_interest_api_url = "/kif42/api/ak/-1/indicate-interest/" + response = self.client.post(invalid_interest_api_url) + self.assertEqual(response.status_code, 404, f"Invalid URL reachable ({interest_api_url})") + +