From 5e0cad0420154397659f9b97bdae5eacd007cb79 Mon Sep 17 00:00:00 2001 From: rine <k.schmid@liqd.net> Date: Mon, 7 Nov 2022 11:39:37 +0100 Subject: [PATCH 1/2] tests/budgeting: add tests for ordering (default) filters in different phases --- .../budgeting/test_proposals_api_filtering.py | 107 ++++++++++++++++-- tests/budgeting/test_views_integration.py | 36 +++++- 2 files changed, 129 insertions(+), 14 deletions(-) diff --git a/tests/budgeting/test_proposals_api_filtering.py b/tests/budgeting/test_proposals_api_filtering.py index 166a0ac852..2fbe356af4 100644 --- a/tests/budgeting/test_proposals_api_filtering.py +++ b/tests/budgeting/test_proposals_api_filtering.py @@ -1,3 +1,5 @@ +from datetime import timedelta + import pytest from dateutil.parser import parse from django.urls import reverse @@ -11,11 +13,19 @@ @pytest.mark.django_db -def test_proposal_list_mixins(apiclient, phase_factory, proposal_factory, - category_factory, label_factory): - phase, module, project, proposal = setup_phase(phase_factory, - proposal_factory, - phases.SupportPhase) +def test_proposal_list_filter_mixin(apiclient, phase_factory, proposal_factory, + category_factory, label_factory): + support_phase, module, project, proposal = setup_phase(phase_factory, + proposal_factory, + phases.SupportPhase) + voting_phase = phase_factory( + phase_content=phases.VotingPhase(), + module=module, + start_date=support_phase.end_date + timedelta(hours=2), + end_date=support_phase.end_date + timedelta(hours=3), + ) + between_phases = support_phase.end_date + timedelta(hours=1) + category1 = category_factory(module=module) category2 = category_factory(module=module) @@ -63,7 +73,6 @@ def test_proposal_list_mixins(apiclient, phase_factory, proposal_factory, # with labels label1 = label_factory(module=module) label2 = label_factory(module=module) - response = apiclient.get(url) assert 'filters' in response.data assert len(response.data['filters']) == 5 @@ -73,13 +82,33 @@ def test_proposal_list_mixins(apiclient, phase_factory, proposal_factory, assert (str(label2.pk), label2.name) in \ response.data['filters']['labels']['choices'] - with freeze_phase(phase): + # ordering choices and default in different phases + with freeze_phase(support_phase): + response = apiclient.get(url) + assert response.data['filters']['ordering']['choices'] == \ + [('-created', _('Most recent')), + ('-positive_rating_count', _('Most support')), + ('-comment_count', _('Most commented')), + ('dailyrandom', _('Random'))] + assert response.data['filters']['ordering']['default'] == 'dailyrandom' + + with freeze_time(between_phases): response = apiclient.get(url) - assert response.data['filters']['ordering']['choices'] == \ - [('-created', _('Most recent')), - ('-positive_rating_count', _('Most support')), - ('-comment_count', _('Most commented')), - ('dailyrandom', _('Random'))] + assert response.data['filters']['ordering']['choices'] == \ + [('-created', _('Most recent')), + ('-positive_rating_count', _('Most support')), + ('-comment_count', _('Most commented')), + ('dailyrandom', _('Random'))] + assert response.data['filters']['ordering']['default'] == \ + '-positive_rating_count' + + with freeze_phase(voting_phase): + response = apiclient.get(url) + assert response.data['filters']['ordering']['choices'] == \ + [('-created', _('Most recent')), + ('-comment_count', _('Most commented')), + ('dailyrandom', _('Random'))] + assert response.data['filters']['ordering']['default'] == 'dailyrandom' phase_factory(phase_content=phases.RatingPhase(), module=module) response = apiclient.get(url) @@ -408,6 +437,60 @@ def test_proposal_ordering_filter( assert ordered_pks == [8, 2, 4, 7, 6, 5, 1, 3] +@pytest.mark.django_db +def test_proposal_default_ordering_filter( + apiclient, module, phase_factory, proposal_factory, + rating_factory): + + support_phase = phase_factory( + phase_content=phases.SupportPhase(), + module=module, + start_date=parse('2022-01-01 00:00:00 UTC'), + end_date=parse('2022-01-01 10:00:00 UTC'), + ) + voting_phase = phase_factory( + phase_content=phases.VotingPhase(), + module=module, + start_date=parse('2022-01-01 14:00:00 UTC'), + end_date=parse('2022-01-01 18:00:00 UTC'), + ) + + between_phases = parse('2022-01-01 12:00:00 UTC') + + url = reverse('proposals-list', + kwargs={'module_pk': module.pk}) + + proposal_factory(pk=1, + module=module) + proposal_2 = proposal_factory(pk=2, + module=module) + proposal_3 = proposal_factory(pk=3, + module=module) + + rating_factory(content_object=proposal_2, value=1) + rating_factory(content_object=proposal_2, value=1) + rating_factory(content_object=proposal_3, value=1) + + # dailyrandom is default during support + # dailyrandom order for '2022-01-01' is [2, 1, 3] + with freeze_phase(support_phase): + response = apiclient.get(url) + ordered_pks = [proposal['pk'] for proposal in response.data['results']] + assert ordered_pks == [2, 1, 3] + + # support is default between support and voting phase + with freeze_time(between_phases): + response = apiclient.get(url) + ordered_pks = [proposal['pk'] for proposal in response.data['results']] + assert ordered_pks == [2, 3, 1] + + # dailyrandom is default during voting + with freeze_phase(voting_phase): + response = apiclient.get(url) + ordered_pks = [proposal['pk'] for proposal in response.data['results']] + assert ordered_pks == [2, 1, 3] + + @pytest.mark.django_db def test_proposal_filter_combinations( apiclient, module, proposal_factory, category_factory, diff --git a/tests/budgeting/test_views_integration.py b/tests/budgeting/test_views_integration.py index fec45faf6b..0b4982562e 100644 --- a/tests/budgeting/test_views_integration.py +++ b/tests/budgeting/test_views_integration.py @@ -1,7 +1,7 @@ import pytest from django.core import mail from django.urls import reverse -from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ from adhocracy4.test.helpers import assert_template_response from adhocracy4.test.helpers import freeze_phase @@ -9,6 +9,7 @@ from adhocracy4.test.helpers import setup_phase from meinberlin.apps.budgeting import models from meinberlin.apps.budgeting import phases +from meinberlin.apps.budgeting import views @pytest.mark.django_db @@ -22,6 +23,37 @@ def test_list_view(client, phase_factory, proposal_factory): response, 'meinberlin_budgeting/proposal_list.html') +@pytest.mark.django_db +def test_list_view_ordering_choices(client, phase_factory, proposal_factory): + phase, module, project, item = setup_phase( + phase_factory, proposal_factory, phases.RatingPhase) + url = project.get_absolute_url() + with freeze_phase(phase): + response = client.get(url) + view = response.context['view'] + ordering_choices = views.get_ordering_choices(view) + assert ordering_choices == ( + ('-created', _('Most recent')), + ('-positive_rating_count', _('Most popular')), + ('-comment_count', _('Most commented')), + ('dailyrandom', _('Random')) + ) + + phase, module, project, item = setup_phase( + phase_factory, proposal_factory, phases.SupportPhase) + url = project.get_absolute_url() + with freeze_phase(phase): + response = client.get(url) + view = response.context['view'] + ordering_choices = views.get_ordering_choices(view) + assert ordering_choices == ( + ('-created', _('Most recent')), + ('-positive_rating_count', _('Most support')), + ('-comment_count', _('Most commented')), + ('dailyrandom', _('Random')) + ) + + @pytest.mark.django_db def test_list_view_token_form(client, user, phase_factory, proposal_factory, voting_token_factory, module_factory): @@ -63,7 +95,7 @@ def test_list_view_token_form(client, user, phase_factory, proposal_factory, response = client.post(url, data) assert 'token' in response.context_data['token_form'].errors - msg = gettext('This token is not valid') + msg = _('This token is not valid') assert msg in response.context_data['token_form'].errors['token'] assert 'voting_token' not in client.session From 85ed36b42314193edb674049fb56b475372096d9 Mon Sep 17 00:00:00 2001 From: rine <k.schmid@liqd.net> Date: Mon, 7 Nov 2022 11:46:02 +0100 Subject: [PATCH 2/2] apps/budgeting//tests/budgeting: change rule view_support to consider different project accesses and test rules view_support and support_proposal --- meinberlin/apps/budgeting/predicates.py | 23 ++- meinberlin/apps/budgeting/rules.py | 2 + tests/budgeting/rules/test_rules_support.py | 181 +++++++++++++++++ .../rules/test_rules_view_support.py | 182 ++++++++++++++++++ 4 files changed, 383 insertions(+), 5 deletions(-) create mode 100644 tests/budgeting/rules/test_rules_support.py create mode 100644 tests/budgeting/rules/test_rules_view_support.py diff --git a/meinberlin/apps/budgeting/predicates.py b/meinberlin/apps/budgeting/predicates.py index 19d05f82c9..8f50ba5ca1 100644 --- a/meinberlin/apps/budgeting/predicates.py +++ b/meinberlin/apps/budgeting/predicates.py @@ -1,8 +1,10 @@ import rules from rules import predicates as rules_predicates +from adhocracy4.modules.predicates import is_allowed_moderate_project from adhocracy4.modules.predicates import is_context_member from adhocracy4.modules.predicates import is_live_context +from adhocracy4.modules.predicates import is_public_context from adhocracy4.modules.predicates import module_is_between_phases from adhocracy4.phases.predicates import has_feature_active from adhocracy4.phases.predicates import phase_allows_delete_vote @@ -35,22 +37,33 @@ def phase_allows_support(user, item): @rules.predicate def is_allowed_support_item(user, item): if item: - return rules_predicates.is_superuser(user) | \ + return is_allowed_moderate_project(user, item) | \ (is_context_member(user, item) & is_live_context(user, item) & phase_allows_support(user, item)) return False +@rules.predicate +def phase_allows_view_support(module, item_class): + if module: + return has_feature_active(module, item_class, 'support') | \ + module_is_between_phases('meinberlin_budgeting:support', + 'meinberlin_budgeting:voting', + module) + return False + + @rules.predicate def is_allowed_view_support(item_class): @rules.predicate def _view_support(user, module): if module: - return has_feature_active(module, item_class, 'support')\ - | module_is_between_phases('meinberlin_budgeting:support', - 'meinberlin_budgeting:voting', - module) + return is_allowed_moderate_project(user, module) | \ + ((is_public_context(user, module) | + is_context_member(user, module)) & + is_live_context(user, module) & + phase_allows_view_support(module, item_class)) return False return _view_support diff --git a/meinberlin/apps/budgeting/rules.py b/meinberlin/apps/budgeting/rules.py index 51ed6e1908..f96b1c99dc 100644 --- a/meinberlin/apps/budgeting/rules.py +++ b/meinberlin/apps/budgeting/rules.py @@ -20,6 +20,8 @@ module_predicates.is_allowed_add_item(models.Proposal) ) +# is_allowed_support_item is needed, because support also uses +# the rating api which checks rate_proposal permission rules.add_perm( 'meinberlin_budgeting.rate_proposal', module_predicates.is_allowed_rate_item | is_allowed_support_item diff --git a/tests/budgeting/rules/test_rules_support.py b/tests/budgeting/rules/test_rules_support.py new file mode 100644 index 0000000000..c555c03a58 --- /dev/null +++ b/tests/budgeting/rules/test_rules_support.py @@ -0,0 +1,181 @@ +from datetime import timedelta + +import pytest +import rules +from freezegun import freeze_time + +from adhocracy4.projects.enums import Access +from adhocracy4.test.helpers import freeze_phase +from adhocracy4.test.helpers import freeze_post_phase +from adhocracy4.test.helpers import freeze_pre_phase +from adhocracy4.test.helpers import setup_phase +from adhocracy4.test.helpers import setup_users +from meinberlin.apps.budgeting import phases + +perm_name = 'meinberlin_budgeting.support_proposal' + + +def test_perm_exists(): + assert rules.perm_exists(perm_name) + + +@pytest.mark.django_db +def test_pre_phase(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.RequestPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.access == Access.PUBLIC + with freeze_pre_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_support_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.SupportPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.access == Access.PUBLIC + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_between_support_and_voting_phase(phase_factory, proposal_factory, + user, admin): + support_phase, module, project, item = setup_phase(phase_factory, + proposal_factory, + phases.SupportPhase) + phase_factory( + phase_content=phases.VotingPhase, + module=module, + start_date=support_phase.end_date + timedelta(hours=2), + end_date=support_phase.end_date + timedelta(hours=3), + type='meinberlin_budgeting:voting' + ) + between_phases = support_phase.end_date + timedelta(hours=1) + + anonymous, moderator, initiator = setup_users(project) + + assert project.access == Access.PUBLIC + with freeze_time(between_phases): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_voting_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.VotingPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.access == Access.PUBLIC + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_rating_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.RatingPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.access == Access.PUBLIC + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_phase_active_project_private(phase_factory, proposal_factory, + user, user2, admin): + phase, _, project, item = setup_phase( + phase_factory, proposal_factory, phases.SupportPhase, + module__project__access=Access.PRIVATE) + anonymous, moderator, initiator = setup_users(project) + + participant = user2 + project.participants.add(participant) + + assert project.access == Access.PRIVATE + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, participant, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_phase_active_project_semipublic(phase_factory, proposal_factory, + user, user2, admin): + phase, _, project, item = setup_phase( + phase_factory, proposal_factory, phases.SupportPhase, + module__project__access=Access.SEMIPUBLIC) + anonymous, moderator, initiator = setup_users(project) + + participant = user2 + project.participants.add(participant) + + assert project.access == Access.SEMIPUBLIC + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, participant, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_phase_active_project_draft(phase_factory, proposal_factory, + user, admin): + phase, _, project, item = setup_phase(phase_factory, proposal_factory, + phases.SupportPhase, + module__project__is_draft=True) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_draft + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) + + +@pytest.mark.django_db +def test_post_phase_project_archived(phase_factory, proposal_factory, + user, admin): + phase, _, project, item = setup_phase(phase_factory, proposal_factory, + phases.SupportPhase, + module__project__is_archived=True) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_archived + with freeze_post_phase(phase): + assert not rules.has_perm(perm_name, anonymous, item) + assert not rules.has_perm(perm_name, user, item) + assert rules.has_perm(perm_name, moderator, item) + assert rules.has_perm(perm_name, initiator, item) + assert rules.has_perm(perm_name, admin, item) diff --git a/tests/budgeting/rules/test_rules_view_support.py b/tests/budgeting/rules/test_rules_view_support.py new file mode 100644 index 0000000000..89903bac69 --- /dev/null +++ b/tests/budgeting/rules/test_rules_view_support.py @@ -0,0 +1,182 @@ +from datetime import timedelta + +import pytest +import rules +from freezegun import freeze_time + +from adhocracy4.projects.enums import Access +from adhocracy4.test.helpers import freeze_phase +from adhocracy4.test.helpers import freeze_post_phase +from adhocracy4.test.helpers import freeze_pre_phase +from adhocracy4.test.helpers import setup_phase +from adhocracy4.test.helpers import setup_users +from meinberlin.apps.budgeting import phases + +perm_name = 'meinberlin_budgeting.view_support' + + +def test_perm_exists(): + assert rules.perm_exists(perm_name) + + +@pytest.mark.django_db +def test_pre_phase(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.CollectPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_public + with freeze_pre_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_support_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.SupportPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_public + with freeze_phase(phase): + assert rules.has_perm(perm_name, anonymous, module) + assert rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_between_support_and_voting_phase(phase_factory, proposal_factory, + user, admin): + support_phase, module, project, item = setup_phase(phase_factory, + proposal_factory, + phases.SupportPhase) + phase_factory( + phase_content=phases.VotingPhase, + module=module, + start_date=support_phase.end_date + timedelta(hours=2), + end_date=support_phase.end_date + timedelta(hours=3), + type='meinberlin_budgeting:voting' + ) + between_phases = support_phase.end_date + timedelta(hours=1) + + anonymous, moderator, initiator = setup_users(project) + + assert project.is_public + with freeze_time(between_phases): + assert rules.has_perm(perm_name, anonymous, module) + assert rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_voting_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.VotingPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_public + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_rating_phase_active(phase_factory, proposal_factory, user, admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.RatingPhase) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_public + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_phase_active_project_private(phase_factory, proposal_factory, + user, user2, admin): + phase, module, project, item = setup_phase( + phase_factory, proposal_factory, phases.SupportPhase, + module__project__access=Access.PRIVATE) + anonymous, moderator, initiator = setup_users(project) + + participant = user2 + project.participants.add(participant) + + assert project.access == Access.PRIVATE + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, participant, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_phase_active_project_semipublic(phase_factory, proposal_factory, + user, user2, admin): + phase, module, project, item = setup_phase( + phase_factory, proposal_factory, phases.SupportPhase, + module__project__access=Access.SEMIPUBLIC) + anonymous, moderator, initiator = setup_users(project) + + participant = user2 + project.participants.add(participant) + + assert project.access == Access.SEMIPUBLIC + with freeze_phase(phase): + assert rules.has_perm(perm_name, anonymous, module) + assert rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, participant, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_phase_active_project_draft(phase_factory, proposal_factory, user, + admin): + phase, module, project, item = setup_phase(phase_factory, proposal_factory, + phases.SupportPhase, + module__project__is_draft=True) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_draft + with freeze_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module) + + +@pytest.mark.django_db +def test_post_phase_project_archived(phase_factory, proposal_factory, user, + admin): + phase, module, project, item = setup_phase( + phase_factory, proposal_factory, + phases.SupportPhase, + module__project__is_archived=True) + anonymous, moderator, initiator = setup_users(project) + + assert project.is_archived + with freeze_post_phase(phase): + assert not rules.has_perm(perm_name, anonymous, module) + assert not rules.has_perm(perm_name, user, module) + assert rules.has_perm(perm_name, moderator, module) + assert rules.has_perm(perm_name, initiator, module) + assert rules.has_perm(perm_name, admin, module)