From 650c2821b25680ef1cb921d46e3c3c53b4356eee Mon Sep 17 00:00:00 2001 From: Hannes Honkasaari Date: Wed, 23 Jun 2021 09:13:43 +0300 Subject: [PATCH] unauth users can answer polls if open commenting --- democracy/tests/conftest.py | 38 ++++++++++++++ democracy/tests/test_section_poll.py | 76 +++++++++++++++++++++++++--- democracy/views/section_comment.py | 26 +++++++--- 3 files changed, 126 insertions(+), 14 deletions(-) diff --git a/democracy/tests/conftest.py b/democracy/tests/conftest.py index 42017ced..d49cd4df 100644 --- a/democracy/tests/conftest.py +++ b/democracy/tests/conftest.py @@ -101,6 +101,44 @@ def default_hearing(john_doe, contact_person, default_organization, default_proj return hearing +@pytest.fixture() +def hearing__with_4_different_commenting(john_doe, contact_person, default_organization, default_project): + """ + Fixture for a "default" hearing with four sections (one main, three other sections). + All objects will have the 3 default images attached. + All objects will have commenting_map_tools all. + Each section will have a different commenting rule. + """ + commenting_restrictions = [Commenting.NONE, Commenting.OPEN, Commenting.REGISTERED, Commenting.STRONG] + hearing = Hearing.objects.create( + title='Default test hearing One', + open_at=now() - datetime.timedelta(days=1), + close_at=now() + datetime.timedelta(days=1), + slug='default-hearing-slug', + organization=default_organization, + project_phase=default_project.phases.all()[0], + ) + for x in range(1, 5): + section_type = (InitialSectionType.MAIN if x == 1 else InitialSectionType.SCENARIO) + section = Section.objects.create( + abstract='Section %d abstract' % x, + hearing=hearing, + type=SectionType.objects.get(identifier=section_type), + commenting=commenting_restrictions[x-1], + commenting_map_tools=CommentingMapTools.ALL + ) + create_default_images(section) + create_default_files(section) + section.comments.create(created_by=john_doe, content=default_comment_content[::-1]) + section.comments.create(created_by=john_doe, content=red_comment_content[::-1]) + section.comments.create(created_by=john_doe, content=green_comment_content[::-1]) + + assert_ascending_sequence([s.ordering for s in hearing.sections.all()]) + + hearing.contact_persons.add(contact_person) + + return hearing + @pytest.fixture() def hearing_without_comments(contact_person, default_organization, default_project): """ diff --git a/democracy/tests/test_section_poll.py b/democracy/tests/test_section_poll.py index 5d31e7d3..a689a3b9 100644 --- a/democracy/tests/test_section_poll.py +++ b/democracy/tests/test_section_poll.py @@ -9,6 +9,7 @@ from democracy.tests.test_comment import get_comment_data from democracy.tests.test_hearing import valid_hearing_json from democracy.tests.utils import get_data_from_response, assert_common_keys_equal +from democracy.enums import Commenting from sys import platform @@ -170,17 +171,43 @@ def test_update_poll_having_answers(valid_hearing_json_with_poll, john_doe_api_c update_response = john_smith_api_client.put('/v1/hearing/%s/' % data['id'], data=data, format='json') assert update_response.status_code == 400 - +@pytest.mark.parametrize("commenting_restriction", (Commenting.NONE, Commenting.REGISTERED, Commenting.STRONG)) @pytest.mark.django_db -def test_post_section_poll_answer_unauthenticated(api_client, default_hearing, geojson_feature): - section = default_hearing.sections.first() - SectionPollFactory(section=section, option_count=3) - url = '/v1/hearing/%s/sections/%s/comments/' % (default_hearing.id, section.id) +def test_post_section_poll_answer_unauthenticated_but_not_allowed(api_client, hearing__with_4_different_commenting, geojson_feature, commenting_restriction): + section = hearing__with_4_different_commenting.sections.filter(commenting=commenting_restriction).first() + poll = SectionPollFactory(section=section, option_count=3, type=SectionPoll.TYPE_SINGLE_CHOICE) + option = poll.options.all().first() + url = '/v1/hearing/%s/sections/%s/comments/' % (hearing__with_4_different_commenting.id, section.id) data = get_comment_data() - data['answers'] = [{}] + data['answers'] = [{ + 'question': poll.id, + 'type': SectionPoll.TYPE_SINGLE_CHOICE, + 'answers': [option.id], + }] response = api_client.post(url, data=data) assert response.status_code == 403 +@pytest.mark.django_db +def test_post_section_poll_answer_unauthenticated_single_choice(api_client, hearing__with_4_different_commenting, geojson_feature): + section = hearing__with_4_different_commenting.sections.filter(commenting=Commenting.OPEN).first() + poll = SectionPollFactory(section=section, option_count=3, type=SectionPoll.TYPE_SINGLE_CHOICE) + option = poll.options.all().first() + + url = '/v1/hearing/%s/sections/%s/comments/' % (hearing__with_4_different_commenting.id, section.id) + data = get_comment_data() + data['answers'] = [{ + 'question': poll.id, + 'type': SectionPoll.TYPE_SINGLE_CHOICE, + 'answers': [option.id], + }] + response = api_client.post(url, data=data) + created_data = get_data_from_response(response, status_code=201) + poll.refresh_from_db(fields=['n_answers']) + option.refresh_from_db(fields=['n_answers']) + assert poll.n_answers == 1 + assert option.n_answers == 1 + assert created_data['answers'][0]['type'] == data['answers'][0]['type'] + assert set(created_data['answers'][0]['answers']) == set(data['answers'][0]['answers']) @pytest.mark.django_db def test_post_section_poll_answer_single_choice(john_doe_api_client, default_hearing, geojson_feature): @@ -204,6 +231,43 @@ def test_post_section_poll_answer_single_choice(john_doe_api_client, default_hea assert created_data['answers'][0]['type'] == data['answers'][0]['type'] assert set(created_data['answers'][0]['answers']) == set(data['answers'][0]['answers']) +@pytest.mark.parametrize("commenting_restriction", (Commenting.NONE, Commenting.STRONG)) +@pytest.mark.django_db +def test_post_section_poll_answer_authenticated_but_not_allowed(john_doe_api_client, hearing__with_4_different_commenting, geojson_feature, commenting_restriction): + section = hearing__with_4_different_commenting.sections.filter(commenting=commenting_restriction).first() + poll = SectionPollFactory(section=section, option_count=3, type=SectionPoll.TYPE_SINGLE_CHOICE) + option = poll.options.all().first() + url = '/v1/hearing/%s/sections/%s/comments/' % (hearing__with_4_different_commenting.id, section.id) + data = get_comment_data() + data['answers'] = [{ + 'question': poll.id, + 'type': SectionPoll.TYPE_SINGLE_CHOICE, + 'answers': [option.id], + }] + response = john_doe_api_client.post(url, data=data) + assert response.status_code == 403 + +@pytest.mark.django_db +def test_post_section_poll_answer_authenticated_open_commenting(john_doe_api_client, hearing__with_4_different_commenting, geojson_feature): + section = hearing__with_4_different_commenting.sections.filter(commenting=Commenting.OPEN).first() + poll = SectionPollFactory(section=section, option_count=3, type=SectionPoll.TYPE_SINGLE_CHOICE) + option = poll.options.all().first() + + url = '/v1/hearing/%s/sections/%s/comments/' % (hearing__with_4_different_commenting.id, section.id) + data = get_comment_data() + data['answers'] = [{ + 'question': poll.id, + 'type': SectionPoll.TYPE_SINGLE_CHOICE, + 'answers': [option.id], + }] + response = john_doe_api_client.post(url, data=data) + created_data = get_data_from_response(response, status_code=201) + poll.refresh_from_db(fields=['n_answers']) + option.refresh_from_db(fields=['n_answers']) + assert poll.n_answers == 1 + assert option.n_answers == 1 + assert created_data['answers'][0]['type'] == data['answers'][0]['type'] + assert set(created_data['answers'][0]['answers']) == set(data['answers'][0]['answers']) @pytest.mark.django_db def test_post_section_poll_answer_multiple_choice(john_doe_api_client, default_hearing, geojson_feature): diff --git a/democracy/views/section_comment.py b/democracy/views/section_comment.py index 1c5c6743..b22bfc51 100644 --- a/democracy/views/section_comment.py +++ b/democracy/views/section_comment.py @@ -8,6 +8,7 @@ from rest_framework.settings import api_settings from democracy.models import SectionComment, Label, Section, SectionPoll, SectionPollOption, SectionPollAnswer +from democracy.enums import Commenting from democracy.models.section import CommentImage from democracy.views.comment import COMMENT_FIELDS, BaseCommentViewSet, BaseCommentSerializer from democracy.views.label import LabelSerializer @@ -178,12 +179,15 @@ def _check_single_choice_poll(self, answer): def _check_can_vote(self, answer): if not answer['answers']: return None - poll_answers = SectionPollAnswer.objects.filter( - option__poll=answer['question'], - comment__created_by=self.request.user - ) - if poll_answers: - raise ValidationError({'answers': [_('You have already voted.')]}) + + # Authenticated users can only have one answer per poll. + if self.request.user.is_authenticated: + poll_answers = SectionPollAnswer.objects.filter( + option__poll=answer['question'], + comment__created_by=self.request.user + ) + if poll_answers: + raise ValidationError({'answers': [_('You have already voted.')]}) def create_related(self, request, instance=None, *args, **kwargs): answers = request.data.pop('answers', []) @@ -270,9 +274,15 @@ def _check_may_comment(self, request): raise ValidationError({'section': [ _('The comment section has to be specified in URL or by JSON section or comment field.') ]}) - if len(request.data.get('answers', [])) > 0 and not request.user.is_authenticated: + + ''' + Unauthenticated user can answer polls in hearings that have open commenting. + The following if statement should never be true as unauthenticated users can't post comments if + the hearing doesn't have open commenting. + ''' + if len(request.data.get('answers', [])) > 0 and not request.user.is_authenticated and parent.commenting != Commenting.OPEN: return response.Response( - {'status': 'Unauthenticated users cannot answer polls.'}, + {'status': 'Unauthenticated users cannot answer polls in hearings that do not have open commenting.'}, status=status.HTTP_403_FORBIDDEN ) return super()._check_may_comment(request)