From d60db5d4ace5ba44484d43d37553cde981b87773 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Tue, 2 Jun 2026 11:26:35 +0200 Subject: [PATCH 01/12] Remove TestVoteService and TestVoteAdapter --- openslides_backend/services/vote/adapter.py | 4 ---- openslides_backend/services/vote/interface.py | 3 --- tests/system/base.py | 7 +++---- tests/system/util.py | 17 +---------------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/openslides_backend/services/vote/adapter.py b/openslides_backend/services/vote/adapter.py index ddbadc99c5..41c6b2b92b 100644 --- a/openslides_backend/services/vote/adapter.py +++ b/openslides_backend/services/vote/adapter.py @@ -109,10 +109,6 @@ def reset(self, id: int) -> dict[str, Any]: endpoint = self.get_endpoint(id, "reset") return self.retrieve(endpoint) - def vote(self, id: int, payload: dict[str, Any]) -> dict[str, Any]: - endpoint = self.get_endpoint(id, "vote") - return self.retrieve(endpoint, payload=payload) - def get_endpoint(self, id: int | None = None, route: str | None = None) -> str: return ( f"{self.url}/poll" diff --git a/openslides_backend/services/vote/interface.py b/openslides_backend/services/vote/interface.py index ef69f58ad5..7a468a62f0 100644 --- a/openslides_backend/services/vote/interface.py +++ b/openslides_backend/services/vote/interface.py @@ -28,6 +28,3 @@ def finalize( @abstractmethod def reset(self, id: int) -> dict[str, Any]: ... - - @abstractmethod - def vote(self, id: int, payload: dict[str, Any]) -> dict[str, Any]: ... diff --git a/tests/system/base.py b/tests/system/base.py index 68a61baeb4..713c1dec3b 100644 --- a/tests/system/base.py +++ b/tests/system/base.py @@ -26,6 +26,7 @@ from openslides_backend.services.postgresql.db_connection_handling import ( get_new_os_conn, ) +from openslides_backend.services.vote.interface import VoteService from openslides_backend.shared.env import Environment from openslides_backend.shared.exceptions import ( ActionException, @@ -51,8 +52,6 @@ ) from tests.util import AuthData, Client, Response -from .util import TestVoteService - DEFAULT_PASSWORD = "password" ADMIN_USERNAME = "admin" @@ -62,7 +61,7 @@ class BaseSystemTestCase(TestCase): app: OpenSlidesBackendWSGIApplication auth: AuthenticationService - vote_service: TestVoteService + vote_service: VoteService media: Any # Any is needed because it is mocked and has magic methods client: Client anon_client: Client @@ -84,7 +83,7 @@ def setUp(self) -> None: self.env = cast(Environment, self.app.env) self.auth = self.services.authentication() self.media = self.services.media() - self.vote_service = cast(TestVoteService, self.services.vote()) + self.vote_service = self.services.vote() self.set_thread_watch_timeout(-1) self.user_id = 1 diff --git a/tests/system/util.py b/tests/system/util.py index 6fed3c97b8..a169287f45 100644 --- a/tests/system/util.py +++ b/tests/system/util.py @@ -1,6 +1,5 @@ import cProfile import os -from abc import abstractmethod from collections.abc import Callable from typing import Any, Self from unittest.mock import MagicMock, Mock, patch @@ -15,7 +14,6 @@ from openslides_backend.services.database.extended_database import ExtendedDatabase from openslides_backend.services.media.interface import MediaService from openslides_backend.services.vote.adapter import VoteAdapter -from openslides_backend.services.vote.interface import VoteService from openslides_backend.shared.env import Environment, is_truthy from openslides_backend.shared.exceptions import MediaServiceException from openslides_backend.shared.interfaces.services import Services @@ -34,19 +32,6 @@ def convert_to_test_response(response: RequestsResponse) -> Response: ) -class TestVoteService(VoteService): - url: str - - @abstractmethod - def vote(self, poll_id: int, payload: dict[str, Any]) -> Response: ... - - -class TestVoteAdapter(VoteAdapter, TestVoteService): - def vote(self, poll_id: int, payload: dict[str, Any]) -> Response: - endpoint = self.url + f"?id={poll_id}" - return self.retrieve(endpoint, payload) - - def create_action_test_application() -> OpenSlidesBackendWSGIApplication: return create_test_application(ActionView) @@ -75,7 +60,7 @@ def create_test_application(view: type[View]) -> OpenSlidesBackendWSGIApplicatio logging=MagicMock(), ) services.vote = providers.Singleton( - TestVoteAdapter, services.config.vote_url, MagicMock() + VoteAdapter, services.config.vote_url, MagicMock() ) mock_media_service = Mock(MediaService) mock_media_service.upload_mediafile = Mock( From c67620b001d3dfa28d132c95048ef46c7f19ee41 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Tue, 2 Jun 2026 17:08:21 +0200 Subject: [PATCH 02/12] Delete vote.create action --- .../action/actions/vote/create.py | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 openslides_backend/action/actions/vote/create.py diff --git a/openslides_backend/action/actions/vote/create.py b/openslides_backend/action/actions/vote/create.py deleted file mode 100644 index a02bc677fa..0000000000 --- a/openslides_backend/action/actions/vote/create.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import cast - -from openslides_backend.action.util.typing import ActionData -from openslides_backend.services.database.commands import GetManyRequest - -from ....models.models import Vote -from ...mixins.create_action_with_inferred_meeting import ( - CreateActionWithInferredMeeting, -) -from ...util.action_type import ActionType -from ...util.default_schema import DefaultSchema -from ...util.register import register_action - - -@register_action("vote.create", action_type=ActionType.BACKEND_INTERNAL) -class VoteCreate(CreateActionWithInferredMeeting): - """ - Internal action to create a vote. - """ - - model = Vote() - schema = DefaultSchema(Vote()).get_create_schema( - required_properties=[ - "weight", - "value", - "option_id", - "user_token", - ], - optional_properties=["delegated_user_id", "user_id"], - ) - - relation_field_for_meeting = "option_id" - - def prefetch(self, action_data: ActionData) -> None: - self.datastore.get_many( - [ - GetManyRequest( - "option", - list({instance["option_id"] for instance in action_data}), - ["meeting_id", "vote_ids"], - ), - ], - use_changed_models=False, - ) - meeting_users = self.datastore.get_many( - [ - GetManyRequest( - "meeting_user", - list( - { - cast(int, instance.get(fname)) - for instance in action_data - for fname in ( - "meeting_user_id", - "delegated_meeting_user_id", - ) - if instance.get(fname) - } - ), - ["id", "user_id", "vote_ids", "delegated_vote_ids"], - ), - ], - use_changed_models=False, - lock_result=False, - )["meeting_user"] - - self.datastore.get_many( - [ - GetManyRequest( - "user", - list({mu["user_id"] for mu in meeting_users.values()}), - ["id", "poll_voted_ids"], - ), - ], - use_changed_models=False, - lock_result=False, - ) From c5e54a7b5931c2e5df986aa0e7836b6fb695ebb0 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Tue, 2 Jun 2026 17:11:38 +0200 Subject: [PATCH 03/12] Delete docs for deleted actions --- docs/actions/poll.create.md | 82 ------------------------------------- docs/actions/poll.update.md | 57 -------------------------- 2 files changed, 139 deletions(-) delete mode 100644 docs/actions/poll.create.md delete mode 100644 docs/actions/poll.update.md diff --git a/docs/actions/poll.create.md b/docs/actions/poll.create.md deleted file mode 100644 index 5b7a729f3b..0000000000 --- a/docs/actions/poll.create.md +++ /dev/null @@ -1,82 +0,0 @@ -## Payload - -Helper Interface for options to create: -```js -Interface Option { - // Exactly one of text, content_object_id or poll_candidate_user_ids must be given - text: string, // topic-poll - content_object_id: Fqid, // must be one of user or motion. - poll_candidate_user_ids: [user_ids], // sorted list of user ids for candidate list election - - // Optionally and only for type==analog, votes can be given - Y: decimal(6), // Y, YN, YNA mode - N: decimal(6), // N, YN, YNA mode - A: decimal(6) // YNA mode -} -``` - -Payload: -```js -{ -// Required - title: string, - type: string, - pollmethod: string, - - meeting_id: Id, - options: Option[], // must have at least one entry. - -// Optional - content_object_id: Fqid, - description: string, - min_votes_amount: number, - max_votes_amount: number, - max_votes_per_option: number, - global_yes: boolean, - global_no: boolean, - global_abstain: boolean, - onehundred_percent_base: string, - backend: string, - -// Optional, only for type==named - live_voting_enabled: boolean, - -// Only for non analog types - entitled_group_ids: Id[], - -// Only for type==analog - publish_immediately: boolean, - -// Optionally and only for type==analog, votes can be given - votesvalid: decimal(6), - votesinvalid: decimal(6), - votescast: decimal(6), - amount_global_yes: decimal(6), - amount_global_no: decimal(6), - amount_global_abstain: decimal(6) -} -``` - -## Action -If an analog poll with votes is given, the state is set to `finished` if at least one vote value is given. if `publish_immediately` is true and some vote value is given, the state is set to `published`. All options given are created as instances of the `option` model. If some options have values (for analog polls), `vote` objects have to be created, one for each option and vote value (`Y`, `N`, `A`). - -The options must be unique in the way that each non-empty `text` and non-empty `content_object_id` can only exist once. The `option/weight` is set in the order the options are given in the payload. A global option is created. - -If the `type` is `pseudoanonymous`, `is_pseudoanonymized` is set to `true`. - -If the `content_object_id` points to a `motion` and the `motion_state` of the motion misses `allow_create_poll`, it is forbidden to create a poll. - -The `entitled_group_ids` may not contain the meetings `anonymous_group_id`. - -The `max_votes_per_option` and `min_votes_amount` must be smaller or equal to `max_votes_amount`. - -The `live_voting_enabled` could be set for named motion and following named assignment polls: -- polls with pollmethod `Y` and not `global_yes` and `max_votes_amount` of 1 -- polls with pollmethod `YNA` or `YN` and only one option -- list polls - -## Permissions -The request user needs: -- `motion.can_manage_polls` if the poll's content object is a motion -- `assignment.can_manage_polls` if the poll's content object is an assignment -- `poll.can_manage` if the poll's content object is a topic diff --git a/docs/actions/poll.update.md b/docs/actions/poll.update.md deleted file mode 100644 index 5c4953d4b5..0000000000 --- a/docs/actions/poll.update.md +++ /dev/null @@ -1,57 +0,0 @@ -## Payload -```js -{ -// Required - id: Id, - -// Optional, only if state == created - pollmethod: string, - min_votes_amount: number, - max_votes_amount: number, - max_votes_per_option: number, - global_yes: boolean, - global_no: boolean, - global_abstain: boolean, - backend: string, - -// Optional, only if state == created, only for non analog types - entitled_group_ids: Id[], - -// Optional, every state - title: string, - description: string, - onehundred_percent_base: string, - -// Optional, type==analog, every state - votesvalid: number, - votesinvalid: number, - votescast: number, - publish_immediately: boolean, - -// Optional, type==named - live_voting_enabled: boolean - -// action called internally - entitled_users_at_stop: json -} -``` - -## Action -For analog polls: If the state is created and at least one vote value is given (`votesvalid`/`votesinvalid`/`votescast`), the state is set to finished. If additionally `publish_immediately` is given, the state is set to published. - -For electronic polls some fields can only be updated, if the state is *created*. - -The `entitled_group_ids` may not contain the meetings `anonymous_group_id`. - -The `max_votes_per_option` and `min_votes_amount` must be smaller or equal to `max_votes_amount` after the model has been updated. - -The `live_voting_enabled` could be set for named motion and following named assignment polls: -- polls with pollmethod `Y` and not `global_yes` and `max_votes_amount` of 1 -- polls with pollmethod `YNA` or `YN` and only one option -- list polls - -## Permissions -The request user needs: -- `motion.can_manage_polls` if the poll's content object is a motion -- `assignment.can_manage_polls` if the poll's content object is an assignment -- `poll.can_manage` if the poll's content object is a topic From 3f3a26d934bcdb1f63758cd79896a5389ac9cb12 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Thu, 4 Jun 2026 08:19:56 +0200 Subject: [PATCH 04/12] Skip migration tests --- tests/system/action/test_migration_route.py | 3 +++ tests/system/migrations/test_0100_init_reldb.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/system/action/test_migration_route.py b/tests/system/action/test_migration_route.py index 940b84c4b3..e5633f9672 100644 --- a/tests/system/action/test_migration_route.py +++ b/tests/system/action/test_migration_route.py @@ -3,6 +3,8 @@ from typing import Any from unittest.mock import Mock, patch +import pytest + from openslides_backend.http.views.action_view import ActionView from openslides_backend.migrations.exceptions import MigrationException from openslides_backend.migrations.migration_handler import MigrationState @@ -18,6 +20,7 @@ from .test_internal_actions import BaseInternalPasswordTest, BaseInternalRequestTest +@pytest.mark.skip(reason="Waiting for the migration framework.") class BaseMigrationRouteTest(BaseInternalRequestTest, BaseMigrationTestCase): """ Uses the anonymous client to call the migration route. diff --git a/tests/system/migrations/test_0100_init_reldb.py b/tests/system/migrations/test_0100_init_reldb.py index 981a7aa4fa..5118bc07a4 100644 --- a/tests/system/migrations/test_0100_init_reldb.py +++ b/tests/system/migrations/test_0100_init_reldb.py @@ -12,6 +12,8 @@ from unittest.mock import Mock, patch from zoneinfo import ZoneInfo +import pytest + from meta.dev.src.generate_sql_schema import GenerateCodeBlocks from openslides_backend.http.application import OpenSlidesBackendWSGIApplication from openslides_backend.http.views import ActionView @@ -64,6 +66,7 @@ data: dict[str, any] = {} +@pytest.mark.skip(reason="Waiting for the migration framework.") class TestMigration100(BaseMigrationTestCase): """ Commentary: From 155b1165100507afbf710065db25bdd405b12b1a Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Wed, 3 Jun 2026 22:39:16 +0200 Subject: [PATCH 05/12] Adjust export_helper and test_meeting_export --- openslides_backend/shared/export_helper.py | 77 +++------- tests/system/presenter/test_export_meeting.py | 140 +++++++++++++++--- 2 files changed, 143 insertions(+), 74 deletions(-) diff --git a/openslides_backend/shared/export_helper.py b/openslides_backend/shared/export_helper.py index ada4b93633..d24f97f2a7 100644 --- a/openslides_backend/shared/export_helper.py +++ b/openslides_backend/shared/export_helper.py @@ -15,7 +15,7 @@ RelationField, RelationListField, ) -from ..models.models import Meeting, PollBallot +from ..models.models import Meeting from ..services.database.commands import GetManyRequest from ..services.database.interface import Database from .patterns import collection_from_fqid, fqid_from_collection_and_id, id_from_fqid @@ -208,62 +208,29 @@ def export_meeting( user_ids.add(results["meeting_user"][id_]["user_id"]) add_users(list(user_ids), export, meeting_id, datastore, internal_target) - # Get related ballots + # Get related configs, options and ballots if polls := export.get("poll"): - ballot_ids = [ - ballot_id - for poll in polls.values() - for ballot_id in poll.get("ballot_ids", []) - ] - if ballot_ids: - ballots = datastore.get_many( - [ - GetManyRequest( - "poll_ballot", ballot_ids, get_fields_for_export("poll_ballot") - ) - ], - lock_result=False, - use_changed_models=False, - )["poll_ballot"] - for ballot_id, instance in ballots.items(): - export.setdefault("poll_ballot", {})[str(ballot_id)] = instance - for field_name, value in instance.items(): - model_field = PollBallot().get_field(field_name) - if ( - not isinstance(model_field, RelationField) - or model_field.get_own_field_name() == "poll_id" - ): - continue - collection, relation_field = next(iter(model_field.to.items())) - export[collection][str(value)].setdefault( - relation_field, [] - ).append(ballot_id) - - config_ids = [poll.get("config_id") for poll in polls.values()] - - for config_fqid in config_ids: - collection, id_ = config_fqid.split("/") - instance = datastore.get( - config_fqid, - list(get_fields_for_export(collection)), - lock_result=False, - use_changed_models=False, + related_collections: dict[str, list[int]] = {} + for poll in polls.values(): + for collection_base_name in ["ballot", "option"]: + if ids := poll.get(f"{collection_base_name}_ids"): + related_collections.setdefault( + f"poll_{collection_base_name}", list() + ).extend(ids) + config_collection, config_id = poll["config_id"].split("/") + related_collections.setdefault(config_collection, list()).append( + int(config_id) + ) + related_models = datastore.get_many( + [ + GetManyRequest(collection, ids, get_fields_for_export(collection)) + for collection, ids in related_collections.items() + ] + ) + for collection, data in related_models.items(): + export.setdefault(collection, {}).update( + {str(id_): instance_data for id_, instance_data in data.items()} ) - export.setdefault(collection, {})[str(id_)] = instance - if option_ids := instance.get("option_ids"): - options = datastore.get_many( - [ - GetManyRequest( - "poll_config_option", - option_ids, - get_fields_for_export("poll_config_option"), - ) - ], - lock_result=False, - use_changed_models=False, - )["poll_config_option"] - for id_, option in options.items(): - export.setdefault("poll_config_option", {})[str(id_)] = option # Sort instances by id within each collection for collection, instances in export.items(): diff --git a/tests/system/presenter/test_export_meeting.py b/tests/system/presenter/test_export_meeting.py index bfe5903e2c..27a99a5418 100644 --- a/tests/system/presenter/test_export_meeting.py +++ b/tests/system/presenter/test_export_meeting.py @@ -223,17 +223,15 @@ def test_export_meeting_find_special_users(self) -> None: Find meeting_users in: Collection | Field poll | voted_ids - motion | supporter_meeting_user_ids ballot | acting_meeting_user_id ballot | represented_meeting_user_id - poll_config_option | meeting_user_id + poll_option | meeting_user_id """ - self.create_meeting() self.create_motion(1, 30) self.set_models( { "meeting/1": {"present_user_ids": [11]}, - "group/1": {"meeting_user_ids": [112, 114]}, + "group/1": {"meeting_user_ids": [112, 113, 114]}, "user/11": {"username": "exuser11"}, "user/12": {"username": "exuser12"}, "user/13": {"username": "exuser13"}, @@ -245,27 +243,28 @@ def test_export_meeting_find_special_users(self) -> None: "meeting_user_id": 112, }, "meeting_user/113": {"meeting_id": 1, "user_id": 13}, - "meeting_user/114": {"meeting_id": 1, "user_id": 14}, + "meeting_user/114": { + "meeting_id": 1, + "user_id": 14, + "poll_voted_ids": [80], + }, "poll/80": { "title": "Poll 80", "meeting_id": 1, - "content_object_id": "assignment/10", + "content_object_id": "motion/30", "visibility": Poll.VISIBILITY_NAMED, "config_id": "poll_config_approval/90", - "state": Poll.STATE_STARTED, - "voted_ids": [114], + "state": Poll.STATE_FINISHED, }, - "poll_config_approval/90": {"poll_id": 80}, - "poll_config_option/100": { - "poll_config_id": "poll_config_approval/90", - "meeting_user_id": 113, + "poll_config_approval/90": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID }, - "ballot/120": { + "poll_option/100": {"poll_id": 80, "meeting_user_id": 113}, + "poll_ballot/120": { "poll_id": 80, "value": "yes", "represented_meeting_user_id": 114, "acting_meeting_user_id": 114, - "meeting_id": 1, }, } ) @@ -280,19 +279,122 @@ def test_export_meeting_find_special_users(self) -> None: assert data["meeting"]["1"]["present_user_ids"] == [11] assert data["user"]["11"]["is_present_in_meeting_ids"] == [1] - assert data["motion"]["30"]["supporter_meeting_user_ids"] == [112] - assert data["meeting_user"]["112"]["supported_motion_ids"] == [30] - - assert data["poll_config_option"]["100"]["meeting_user_id"] == 113 + assert data["poll_option"]["100"]["meeting_user_id"] == 113 assert data["meeting_user"]["113"]["poll_option_ids"] == [100] assert data["poll"]["80"]["voted_ids"] == [114] assert data["meeting_user"]["114"]["poll_voted_ids"] == [80] - assert data["poll_ballot"]["120"]["c"] == 114 + assert data["poll_ballot"]["120"]["acting_meeting_user_id"] == 114 assert data["poll_ballot"]["120"]["represented_meeting_user_id"] == 114 assert data["meeting_user"]["114"]["acting_ballot_ids"] == [120] assert data["meeting_user"]["114"]["represented_ballot_ids"] == [120] + def create_assignment(self, meeting_id: int, base: int) -> None: + self.set_models( + { + f"assignment/{base}": { + "title": "just do it", + "meeting_id": meeting_id, + }, + f"list_of_speakers/{base + 100}": { + "content_object_id": f"assignment/{base}", + "meeting_id": meeting_id, + }, + } + ) + + def test_export_meeting_find_poll_configs(self) -> None: + configs_data = { + "poll_config_approval/1": { + "poll_id": 11, + "allow_abstain": False, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, + }, + "poll_config_selection/2": { + "poll_id": 12, + "max_options_amount": 1, + "min_options_amount": 1, + "allow_nota": True, + "strike_out": True, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_ENTITLED, + }, + "poll_config_rating_score/3": { + "poll_id": 13, + "min_options_amount": 1, + "max_options_amount": 2, + "max_votes_per_option": 3, + "min_vote_sum": 1, + "max_vote_sum": 3, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_ENTITLED_PRESENT, + }, + "poll_config_rating_approval/4": { + "poll_id": 14, + "min_options_amount": 1, + "max_options_amount": 3, + "allow_abstain": False, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_ENTITLED_PRESENT, + }, + } + self.create_motion(1, 21) + self.create_motion(1, 24) + self.create_assignment(1, 22) + self.create_assignment(1, 23) + self.create_user_for_meeting(1) + self.create_user_for_meeting(1) + + self.set_models( + { + **configs_data, + "poll/11": { + "title": "Poll 11", + "meeting_id": 1, + "content_object_id": "motion/21", + "config_id": "poll_config_approval/1", + "visibility": Poll.VISIBILITY_NAMED, + "state": Poll.STATE_CREATED, + }, + "poll/12": { + "title": "Poll 12", + "meeting_id": 1, + "content_object_id": "assignment/22", + "config_id": "poll_config_selection/2", + "visibility": Poll.VISIBILITY_OPEN, + "state": Poll.STATE_STARTED, + }, + "poll/13": { + "title": "Poll 13", + "meeting_id": 1, + "content_object_id": "assignment/23", + "config_id": "poll_config_rating_score/3", + "visibility": Poll.VISIBILITY_SECRET, + "state": Poll.STATE_FINISHED, + }, + "poll/14": { + "title": "Poll 14", + "meeting_id": 1, + "content_object_id": "motion/24", + "config_id": "poll_config_rating_approval/4", + "visibility": Poll.VISIBILITY_NAMED, + "state": Poll.STATE_STARTED, + }, + "poll_option/121": {"poll_id": 12, "meeting_user_id": 1}, + "poll_option/122": {"poll_id": 12, "meeting_user_id": 2}, + "poll_option/131": {"poll_id": 13, "meeting_user_id": 1}, + "poll_option/132": {"poll_id": 13, "meeting_user_id": 2}, + "poll_option/141": {"poll_id": 14, "text": "Blue"}, + "poll_option/142": {"poll_id": 14, "text": "Green"}, + "poll_option/143": {"poll_id": 14, "text": "Red"}, + "group/1": {"poll_ids": [11, 12, 13, 14]}, + } + ) + status_code, data = self.request("export_meeting", {"meeting_id": 1}) + assert status_code == 200 + for config_fqid, config_data in configs_data.items(): + config_collection, config_id = config_fqid.split("/") + assert data["poll"][str(config_data["poll_id"])]["config_id"] == config_fqid + for k, v in config_data.items(): + assert data[config_collection][config_id][k] == v + def test_with_structured_published_orga_files(self) -> None: self.set_models( { From 0c0ae78e1a53dc63fbdc536b4d319c2dd9d3aaba Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Thu, 4 Jun 2026 22:12:42 +0200 Subject: [PATCH 06/12] Decrease coverage level --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1541e42be2..bc3405e198 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ exclude_lines = raise NotImplementedError @abstractmethod ^\s*...$ -fail_under = 96 +fail_under = 93 omit = tests/* migrations/* From 224eb42170e79a236c1e1df02e2f66d49573be9c Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Fri, 5 Jun 2026 16:25:56 +0200 Subject: [PATCH 07/12] Remove unused vote adapter methods --- openslides_backend/services/vote/adapter.py | 36 +++---------------- openslides_backend/services/vote/interface.py | 19 +--------- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/openslides_backend/services/vote/adapter.py b/openslides_backend/services/vote/adapter.py index 41c6b2b92b..7868ca596e 100644 --- a/openslides_backend/services/vote/adapter.py +++ b/openslides_backend/services/vote/adapter.py @@ -1,5 +1,5 @@ from enum import StrEnum -from typing import Any, Literal +from typing import Any import requests import simplejson as json @@ -79,39 +79,13 @@ def make_request( ) raise VoteServiceException(f"Cannot reach the vote service on {endpoint}.") - def create(self, payload: dict[str, Any]) -> dict[str, Any]: - endpoint = self.get_endpoint() - return self.retrieve(endpoint, payload=payload) - - def update(self, id: int, payload: dict[str, Any]) -> dict[str, Any]: - endpoint = self.get_endpoint(id) - return self.retrieve(endpoint, payload=payload) - - def delete(self, id: int) -> dict[str, Any]: - endpoint = self.get_endpoint(id) - return self.retrieve(endpoint, RequestMethod.DELETE) - - def start(self, id: int) -> dict[str, Any]: - endpoint = self.get_endpoint(id, "start") - return self.retrieve(endpoint) - - def finalize( - self, - id: int, - optional_attributes: list[Literal["publish", "anonymize"]] = [], - ) -> dict[str, Any]: - endpoint = self.get_endpoint(id, "finalize") - if optional_attributes: - endpoint += f"?{'&'.join(optional_attributes)}" - return self.retrieve(endpoint) - - def reset(self, id: int) -> dict[str, Any]: - endpoint = self.get_endpoint(id, "reset") - return self.retrieve(endpoint) - def get_endpoint(self, id: int | None = None, route: str | None = None) -> str: return ( f"{self.url}/poll" + (f"/{id}" if id else "") + (f"/{route}" if route else "") ) + + def delete(self, id: int) -> dict[str, Any]: + endpoint = self.get_endpoint(id) + return self.retrieve(endpoint, RequestMethod.DELETE) diff --git a/openslides_backend/services/vote/interface.py b/openslides_backend/services/vote/interface.py index 7a468a62f0..7cc2844b30 100644 --- a/openslides_backend/services/vote/interface.py +++ b/openslides_backend/services/vote/interface.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Any, Literal, Protocol +from typing import Any, Protocol from ..shared.authenticated_service import AuthenticatedServiceInterface @@ -9,22 +9,5 @@ class VoteService(AuthenticatedServiceInterface, Protocol): Interface of the vote service. """ - @abstractmethod - def create(self, payload: dict[str, Any]) -> dict[str, Any]: ... - - @abstractmethod - def update(self, id: int, payload: dict[str, Any]) -> dict[str, Any]: ... - @abstractmethod def delete(self, id: int) -> dict[str, Any]: ... - - @abstractmethod - def start(self, id: int) -> dict[str, Any]: ... - - @abstractmethod - def finalize( - self, id: int, optional_attributes: list[Literal["publish", "anonymize"]] = [] - ) -> dict[str, Any]: ... - - @abstractmethod - def reset(self, id: int) -> dict[str, Any]: ... From 4e3233986ac6da9a4277c9a386e0b968f1c9ea28 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Thu, 4 Jun 2026 18:50:21 +0200 Subject: [PATCH 08/12] Complete simple cases --- docs/actions/meeting.create.md | 1 - docs/actions/meeting.update.md | 3 - docs/actions/user.merge_together.md | 2 +- openslides_backend/models/checker.py | 4 +- tests/database/reader/system/util.py | 1 - .../system/action/agenda_item/test_forward.py | 22 +++--- tests/system/action/meeting/test_archive.py | 2 +- tests/system/action/meeting/test_clone.py | 73 ++++++++++--------- tests/system/action/meeting/test_create.py | 9 +-- tests/system/action/meeting/test_delete.py | 16 ++-- tests/system/action/meeting/test_import.py | 12 +-- tests/system/action/meeting/test_update.py | 8 -- .../system/action/meeting_user/test_update.py | 20 ++--- .../test_delete_history_information.py | 48 +++++++----- tests/system/action/projector/test_toggle.py | 2 +- tests/system/action/user/test_delete.py | 39 ++-------- tests/system/presenter/test_check_database.py | 15 ++-- .../presenter/test_check_database_all.py | 15 ++-- tests/system/presenter/test_export_meeting.py | 9 +-- 19 files changed, 133 insertions(+), 168 deletions(-) diff --git a/docs/actions/meeting.create.md b/docs/actions/meeting.create.md index 650cff4f90..ef701df30f 100644 --- a/docs/actions/meeting.create.md +++ b/docs/actions/meeting.create.md @@ -43,7 +43,6 @@ The field `is_active_in_organization_id` is set to the organization_id. If a meeting is created, * `motion_poll_default_type` is `pseudoanonymous` -* `motion_poll_default_method` is `YNA` * `assignment_poll_default_type` is `pseudoanonymous` * `assignment_poll_default_method` is `Y` * `assignment_poll_default_group_ids`, `motion_poll_default_group_ids` and `topic_poll_default_group_ids` have the Delegates group. diff --git a/docs/actions/meeting.update.md b/docs/actions/meeting.update.md index 062bc54950..2b88ab16ac 100644 --- a/docs/actions/meeting.update.md +++ b/docs/actions/meeting.update.md @@ -121,10 +121,8 @@ motion_poll_ballot_paper_selection: string; motion_poll_ballot_paper_number: number; motion_poll_default_type: string; - motion_poll_default_method: string; motion_poll_default_onehundred_percent_base: string; motion_poll_default_group_ids: Id[]; - motion_poll_default_backend: string; motion_poll_projection_name_order_first: string; motion_poll_projection_max_columns: number; @@ -158,7 +156,6 @@ assignment_poll_default_method: string; assignment_poll_default_onehundred_percent_base: string; assignment_poll_default_group_ids: Id[]; - assignment_poll_default_backend: string; topic_poll_default_group_ids: Id[]; diff --git a/docs/actions/user.merge_together.md b/docs/actions/user.merge_together.md index 0f052932e0..5846030064 100644 --- a/docs/actions/user.merge_together.md +++ b/docs/actions/user.merge_together.md @@ -44,7 +44,7 @@ An error is thrown if: - There are conflicts regarding polls, i.e. two or more of the selected users... - Any affected meeting_users have groups that are currently entitled to work on any poll - Any affected meeting_users _who share a meeting_: - - are meeting_users of the poll_config_option on the same poll + - are meeting_users of the poll_option on the same poll - have voted on the same poll (delegated or not) - have running speakers - are in a meeting without `list_of_speakers_allow_multiple_speakers` and have waiting speakers on the same list who cannot be merged together into at most one point_of_order and one normal speech. Speeches may not be merged if there are multiple different values (empty does count) within any of the fields: diff --git a/openslides_backend/models/checker.py b/openslides_backend/models/checker.py index ac0abdfdde..ecc1709225 100644 --- a/openslides_backend/models/checker.py +++ b/openslides_backend/models/checker.py @@ -192,12 +192,12 @@ def check_timestamp(value: Any) -> bool: "meeting", "user", "mediafile", - "vote", + "poll_ballot", "poll_config_approval", "poll_config_selection", "poll_config_rating_score", "poll_config_rating_approval", - "poll_config_option", + "poll_option", } diff --git a/tests/database/reader/system/util.py b/tests/database/reader/system/util.py index 9e153c4bab..6088b202f0 100644 --- a/tests/database/reader/system/util.py +++ b/tests/database/reader/system/util.py @@ -233,7 +233,6 @@ "meeting.can_see_history", "motion.can_manage", "motion.can_manage_polls", - "poll.can_manage", "projector.can_manage", "tag.can_manage", "user.can_manage", diff --git a/tests/system/action/agenda_item/test_forward.py b/tests/system/action/agenda_item/test_forward.py index 6142140b1c..c5a893ef8d 100644 --- a/tests/system/action/agenda_item/test_forward.py +++ b/tests/system/action/agenda_item/test_forward.py @@ -3293,22 +3293,22 @@ def test_forward_structure_level_transfer_only_via_meeting_users(self) -> None: "meeting_id": 1, "title": "Will not transfer", "visibility": Poll.VISIBILITY_SECRET, - "config_id": "poll_config_rating_approval/34", + "config_id": "poll_config_rating_score/34", "state": Poll.STATE_CREATED, - "min_votes_amount": 1, - "max_votes_amount": 1, - "max_votes_per_option": 1, - "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_Y, "content_object_id": "topic/11", }, - "poll_config_approval/34": {"poll_id": 1, "allow_abstain": False}, - "poll_config_option/123": { - "meeting_id": 1, + "poll_config_rating_score/34": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, + "min_options_amount": 1, + "max_options_amount": 1, + "max_votes_per_option": 1, + }, + "poll_option/123": { "poll_id": 1234, "text": "Option A", }, - "poll_config_option/234": { - "poll_config_id": "poll_config_approval/34", + "poll_option/234": { + "poll_id": 1234, "text": "Option B", }, # mediafiles @@ -3488,7 +3488,7 @@ def test_forward_structure_level_transfer_only_via_meeting_users(self) -> None: "meeting_mediafile": 36, "poll": 1235, "poll_config_approval": 35, - "poll_config_option": 235, + "poll_option": 235, "speaker": 38, "structure_level_list_of_speakers": 15, "point_of_order_category": 8, diff --git a/tests/system/action/meeting/test_archive.py b/tests/system/action/meeting/test_archive.py index 31939399c2..43e2f3b133 100644 --- a/tests/system/action/meeting/test_archive.py +++ b/tests/system/action/meeting/test_archive.py @@ -115,7 +115,7 @@ def create_poll(self, base: int, state: str, published: bool = False) -> None: "published": published, }, f"poll_config_approval/{base}": { - "poll_id": base, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, }, } ) diff --git a/tests/system/action/meeting/test_clone.py b/tests/system/action/meeting/test_clone.py index 9a7a18c458..9680445b7a 100644 --- a/tests/system/action/meeting/test_clone.py +++ b/tests/system/action/meeting/test_clone.py @@ -477,10 +477,8 @@ def test_clone_with_change_recommendation_complex(self) -> None: "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_default_group_ids": [], - "motion_poll_default_backend": "fast", }, ) self.set_user_groups(1, [5]) @@ -1385,8 +1383,7 @@ def test_clone_with_settings(self) -> None: "motion_poll_ballot_paper_selection": "NUMBER_OF_DELEGATES", "motion_poll_ballot_paper_number": 42, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YN", + "motion_poll_default_onehundred_percent_base": "yes_no", "users_enable_presence_view": True, "users_enable_vote_weight": True, "users_enable_vote_delegations": True, @@ -1409,7 +1406,7 @@ def test_clone_with_settings(self) -> None: "assignment_poll_sort_poll_result_by_votes": True, "assignment_poll_default_type": "pseudoanonymous", "assignment_poll_default_method": "YNA", - "assignment_poll_default_onehundred_percent_base": "YNA", + "assignment_poll_default_onehundred_percent_base": "valid", } self.meeting_data.update(settings) self.set_test_data_with_admin() @@ -1814,24 +1811,25 @@ def test_clone_vote_delegation(self) -> None: def test_clone_vote_delegated_vote(self) -> None: self.set_test_data() self.create_meeting(4) - self.create_motion(1, 1) - self.create_motion(4, 4) self.set_user_groups(1, [2, 5]) - for poll_id in range(1, 3): + for poll_id in [1, 4]: + self.create_motion(poll_id, poll_id) self.set_models( { f"poll/{poll_id}": { "title": f"Poll {poll_id}", - "meeting_id": 1 if poll_id == 1 else 4, + "meeting_id": poll_id, "content_object_id": f"motion/{poll_id}", "visibility": Poll.VISIBILITY_NAMED, "config_id": f"poll_config_rating_approval/{poll_id}", "state": Poll.STATE_STARTED, }, - f"poll_config_rating_approval/{poll_id}": {"poll_id": poll_id}, - f"ballot/{poll_id}": { - "acting_meeting_user_id": poll_id, - "represented_meeting_user_id": poll_id, + f"poll_config_rating_approval/{poll_id}": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + f"poll_ballot/{poll_id}": { + "acting_meeting_user_id": 1 if poll_id == 1 else 2, + "represented_meeting_user_id": 1 if poll_id == 1 else 2, "poll_id": poll_id, }, }, @@ -1839,23 +1837,26 @@ def test_clone_vote_delegated_vote(self) -> None: response = self.request("meeting.clone", {"meeting_id": 1}) self.assert_status_code(response, 200) self.assert_model_exists( - "ballot/3", + "poll_ballot/5", { - "acting_meeting_user_id": 1, - "represented_meeting_user_id": 1, - "poll_id": 3, + "acting_meeting_user_id": 3, + "represented_meeting_user_id": 3, + "poll_id": 5, }, ) self.assert_model_exists( "user/1", + {"meeting_user_ids": [1, 2, 3], "meeting_ids": [1, 4, 5]}, + ) + self.assert_model_exists( + "meeting_user/3", { - "meeting_user_ids": [1, 2, 3], - # "acting_vote_ids": [1, 2, 3, 4], - # "represented_vote_ids": [1, 2, 3, 4], - "meeting_ids": [1, 4, 5], + "user_id": 1, + "meeting_id": 5, + "acting_ballot_ids": [5], + "represented_ballot_ids": [5], }, ) - self.assert_model_exists("meeting_user/3", {"user_id": 1, "meeting_id": 5}) def test_with_action_worker(self) -> None: """action_worker shouldn't be cloned""" @@ -2077,20 +2078,20 @@ def test_clone_with_list_election(self) -> None: "config_id": "poll_config_approval/1", "state": Poll.STATE_CREATED, }, - "poll_config_approval/1": {"id": 1, "poll_id": 1}, - } - ) - for id_ in range(1, 4): - self.set_models( - { - f"poll_config_option/{id_}": { + "poll_config_approval/1": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + **{ + f"poll_option/{id_}": { "id": id_, "weight": id_, - "poll_config_id": "poll_config_approval/1", "meeting_user_id": id_ + 1, + "poll_id": 1, } - } - ) + for id_ in range(1, 4) + }, + } + ) response = self.request("meeting.clone", {"meeting_id": 1}) self.assert_status_code(response, 200) @@ -2120,14 +2121,14 @@ def test_clone_with_list_election(self) -> None: ) self.assert_model_exists( "poll_config_approval/2", - {"poll_id": 2, "option_ids": [4, 5, 6]}, + {"onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID}, ) for id_ in range(4, 7): self.assert_model_exists( - f"poll_config_option/{id_}", + f"poll_option/{id_}", { "weight": id_ - 3, - "poll_config_id": "poll_config_approval/2", + "poll_id": 2, "meeting_user_id": id_ + 2, }, ) diff --git a/tests/system/action/meeting/test_create.py b/tests/system/action/meeting/test_create.py index 2817620a28..8399fe120d 100644 --- a/tests/system/action/meeting/test_create.py +++ b/tests/system/action/meeting/test_create.py @@ -4,7 +4,7 @@ from openslides_backend.i18n.translator import Translator from openslides_backend.i18n.translator import translate as _ -from openslides_backend.models.models import Meeting +from openslides_backend.models.models import Meeting, Poll from openslides_backend.permissions.management_levels import OrganizationManagementLevel from openslides_backend.shared.util import ONE_ORGANIZATION_FQID from tests.system.action.base import BaseActionTestCase @@ -206,10 +206,9 @@ def test_check_action_data_fields(self) -> None: "start_time": datetime.fromtimestamp(1608120653, ZoneInfo("UTC")), "end_time": datetime.fromtimestamp(1608121653, ZoneInfo("UTC")), "external_id": external_id, - "assignment_poll_default_type": "pseudoanonymous", - "assignment_poll_default_method": "Y", - "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", + "assignment_poll_default_type": Poll.VISIBILITY_SECRET, + "assignment_poll_default_method": Poll.METHOD_SELECTION, + "motion_poll_default_type": Poll.VISIBILITY_SECRET, }, ) diff --git a/tests/system/action/meeting/test_delete.py b/tests/system/action/meeting/test_delete.py index 9aa2c0b030..1f4ea2ae4c 100644 --- a/tests/system/action/meeting/test_delete.py +++ b/tests/system/action/meeting/test_delete.py @@ -252,13 +252,15 @@ def test_delete_with_poll_candidates_and_speakers(self) -> None: "visibility": Poll.VISIBILITY_MANUALLY, "meeting_id": 1, }, - "poll_config_approval/160": {"poll_id": 150}, - "poll_config_option/180": { - "poll_config_id": "poll_config_approval/160", + "poll_config_approval/160": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + "poll_option/180": { + "poll_id": 150, "meeting_user_id": 1, }, - "poll_config_option/181": { - "poll_config_id": "poll_config_approval/160", + "poll_option/181": { + "poll_id": 150, "meeting_user_id": 2, }, "list_of_speakers/190": { @@ -298,8 +300,8 @@ def test_delete_with_poll_candidates_and_speakers(self) -> None: "assignment/140", "poll/150", "poll_config_approval/160", - "poll_config_option/180", - "poll_config_option/181", + "poll_option/180", + "poll_option/181", "list_of_speakers/190", "speaker/210", "speaker/211", diff --git a/tests/system/action/meeting/test_import.py b/tests/system/action/meeting/test_import.py index 53a7dc6004..019d241692 100644 --- a/tests/system/action/meeting/test_import.py +++ b/tests/system/action/meeting/test_import.py @@ -140,10 +140,8 @@ def create_request_data( "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_default_group_ids": [], - "motion_poll_default_backend": "fast", "users_enable_presence_view": True, "users_enable_vote_weight": True, "users_enable_vote_delegations": True, @@ -168,7 +166,6 @@ def create_request_data( "assignment_poll_default_method": "votes", "assignment_poll_default_onehundred_percent_base": "valid", "assignment_poll_default_group_ids": [], - "assignment_poll_default_backend": "fast", "poll_ballot_paper_selection": "CUSTOM_NUMBER", "poll_ballot_paper_number": 8, "poll_sort_poll_result_by_votes": True, @@ -2716,9 +2713,7 @@ def test_import_export_with_orga_mediafiles(self) -> None: "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", - "motion_poll_default_backend": "fast", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_projection_name_order_first": "last_name", "motion_poll_projection_max_columns": 6, "users_enable_presence_view": False, @@ -2739,9 +2734,8 @@ def test_import_export_with_orga_mediafiles(self) -> None: "assignment_poll_default_type": "pseudoanonymous", "assignment_poll_default_method": "Y", "assignment_poll_default_onehundred_percent_base": "valid", - "assignment_poll_default_backend": "fast", "poll_default_type": "analog", - "poll_default_onehundred_percent_base": "YNA", + "poll_default_onehundred_percent_base": "valid", "poll_default_live_voting_enabled": False, "poll_couple_countdown": True, **{field: [1] for field in Meeting.all_default_projectors()}, diff --git a/tests/system/action/meeting/test_update.py b/tests/system/action/meeting/test_update.py index 924227642e..17d09aaa7c 100644 --- a/tests/system/action/meeting/test_update.py +++ b/tests/system/action/meeting/test_update.py @@ -288,14 +288,6 @@ def test_update_applause(self) -> None: self.basic_test(data) self.assert_model_exists("meeting/1", data) - def test_update_poll_default_backend_fields(self) -> None: - data = { - "motion_poll_default_backend": "long", - "assignment_poll_default_backend": "long", - } - self.basic_test(data) - self.assert_model_exists("meeting/1", data) - def test_update_poll_default_live_voting_enabled(self) -> None: self.basic_test({"poll_default_live_voting_enabled": True}) self.assert_model_exists( diff --git a/tests/system/action/meeting_user/test_update.py b/tests/system/action/meeting_user/test_update.py index 4b5717b8a4..91d6d31adc 100644 --- a/tests/system/action/meeting_user/test_update.py +++ b/tests/system/action/meeting_user/test_update.py @@ -108,7 +108,7 @@ def test_update_merge_fields_correct(self) -> None: }, ) - def test_update_with_vote_related_fields(self) -> None: + def test_update_with_poll_related_fields(self) -> None: self.create_meeting() self.set_user_groups(1, [1]) self.create_user("dummy2", [1]) @@ -142,17 +142,19 @@ def test_update_with_vote_related_fields(self) -> None: "sequential_number": 1, "list_of_speakers_id": 1, }, - "poll_config_option/1": { - "poll_config_id": "poll_config_approval/1", + "poll_option/1": { + "poll_id": 1, "meeting_user_id": 2, }, - "poll_config_approval/1": {"poll_id": 1}, - "ballot/1": { + "poll_config_approval/1": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + "poll_ballot/1": { "poll_id": 1, "acting_meeting_user_id": 1, "represented_meeting_user_id": 1, }, - "ballot/2": { + "poll_ballot/2": { "poll_id": 1, "acting_meeting_user_id": 1, "represented_meeting_user_id": 2, @@ -179,9 +181,9 @@ def test_update_with_vote_related_fields(self) -> None: "represented_ballot_ids": [2], }, "poll/1": {"voted_ids": [3]}, - "poll_config_option/1": {"meeting_user_id": 3}, - "ballot/1": {"acting_meeting_user_id": 3}, - "ballot/2": {"represented_meeting_user_id": 3}, + "poll_option/1": {"meeting_user_id": 3}, + "poll_ballot/1": {"acting_meeting_user_id": 3}, + "poll_ballot/2": {"represented_meeting_user_id": 3}, } for fqid, model in expected.items(): self.assert_model_exists(fqid, model) diff --git a/tests/system/action/organization/test_delete_history_information.py b/tests/system/action/organization/test_delete_history_information.py index 7177fadc47..6fab0196cd 100644 --- a/tests/system/action/organization/test_delete_history_information.py +++ b/tests/system/action/organization/test_delete_history_information.py @@ -1,3 +1,6 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + from openslides_backend.models.models import Poll from openslides_backend.permissions.management_levels import OrganizationManagementLevel from tests.system.action.base import BaseActionTestCase @@ -13,6 +16,7 @@ def test_delete_history_information_no_permission(self) -> None: def test_delete_history_information_correct(self) -> None: self.create_meeting() + self.create_user_for_meeting(1) self.set_models( { "organization/1": {"name": "Orga", "enable_electronic_voting": True}, @@ -25,25 +29,35 @@ def test_delete_history_information_correct(self) -> None: "content_object_id": "assignment/1", "meeting_id": 1, }, + "poll/1": { + "title": "test", + "meeting_id": 1, + "content_object_id": "assignment/1", + "visibility": Poll.VISIBILITY_MANUALLY, + "state": Poll.STATE_FINISHED, + "config_id": "poll_config_rating_approval/1", + "result": '{"yes": "3", "no": "2", "abstain": "1"}', + }, + "poll_config_rating_approval/1": { + "poll_id": 1, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, + "allow_abstain": True, + }, + "poll_option/1": {"poll_id": 1, "text": "Delete this item?"}, + "history_position/1": { + "timestamp": datetime.fromtimestamp(1761760881, ZoneInfo("UTC")), + "original_user_id": 1, + "user_id": 1, + }, + "history_entry/1": { + "position_id": 1, + "meeting_id": 1, + "entries": ["Ballot created"], + "original_model_id": "assignment/1", + "model_id": "assignment/1", + }, } ) - vote_service_response = self.vote_service.create( - { - "title": "test", - "visibility": Poll.VISIBILITY_MANUALLY, - "method": Poll.METHOD_RATING_APPROVAL, - "state": Poll.STATE_CREATED, - "meeting_id": 1, - "content_object_id": "assignment/1", - "config": {"allow_abstain": True}, - "result": {"yes": "3", "no": "2", "abstain": "1"}, - }, - ) - self.assertIsNotNone(vote_service_response) - self.assert_history_information("assignment/1", ["Ballot created"]) - for collection in ["history_position", "history_entry"]: - self.assert_model_exists(f"{collection}/1") - self.assert_model_not_exists(f"{collection}/2") response = self.request("organization.delete_history_information", {"id": 1}) self.assert_status_code(response, 200) diff --git a/tests/system/action/projector/test_toggle.py b/tests/system/action/projector/test_toggle.py index 55fc86f6ef..f3f8af0c0b 100644 --- a/tests/system/action/projector/test_toggle.py +++ b/tests/system/action/projector/test_toggle.py @@ -22,8 +22,8 @@ def create_poll(self, base: int) -> None: "content_object_id": "motion/1", }, f"poll_config_rating_approval/{base}": { - "poll_id": base, "allow_abstain": False, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, }, } ) diff --git a/tests/system/action/user/test_delete.py b/tests/system/action/user/test_delete.py index 15d17bc0fa..de43fc7078 100644 --- a/tests/system/action/user/test_delete.py +++ b/tests/system/action/user/test_delete.py @@ -191,7 +191,7 @@ def test_delete_with_submitter(self) -> None: ) self.assert_model_exists("motion/50", {"submitter_ids": [34]}) - def test_delete_with_poll_candidate(self) -> None: + def test_delete_poll_option_meeting_user(self) -> None: self.create_meeting() user_id = self.create_user_for_meeting(1) self.set_models( @@ -213,46 +213,17 @@ def test_delete_with_poll_candidate(self) -> None: "title": "Duckburg town council", "meeting_id": 1, }, - "poll_config_option/1": { - "poll_config_id": "poll_config_approval/1", - "meeting_user_id": 1, + "poll_option/1": {"poll_id": 1, "meeting_user_id": 1}, + "poll_config_approval/1": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, }, - "poll_config_approval/1": {"poll_id": 1}, } ) response = self.request("user.delete", {"id": user_id}) self.assert_status_code(response, 200) self.assert_model_not_exists(f"user/{user_id}") self.assert_model_not_exists("meeting_user/1") - self.assert_model_exists("poll_config_option/1", {"meeting_user_id": None}) - - def test_delete_poll_option_user(self) -> None: - self.create_meeting() - self.create_meeting(4) - self.create_topic(1, 1) - bob_id = self.create_user("bob", [1]) - self.set_models( - { - "poll/1": { - "type": "named", - "pollmethod": "Y", - "backend": "long", - "state": "finished", - "meeting_id": 1, - "content_object_id": "topic/1", - "title": "Poll 1", - "onehundred_percent_base": "YNA", - }, - "option/1": { - "meeting_id": 1, - "poll_id": 1, - "content_object_id": f"user/{bob_id}", - }, - "option/2": {"meeting_id": 1, "poll_id": 1}, - } - ) - response = self.request("user.delete", {"id": bob_id}) - self.assert_status_code(response, 200) + self.assert_model_exists("poll_option/1", {"meeting_user_id": None}) def test_delete_with_group_ids_set_null(self) -> None: self.create_meeting() diff --git a/tests/system/presenter/test_check_database.py b/tests/system/presenter/test_check_database.py index ab863f3e80..f6e4d37112 100644 --- a/tests/system/presenter/test_check_database.py +++ b/tests/system/presenter/test_check_database.py @@ -86,9 +86,7 @@ def get_meeting_defaults(self) -> dict[str, Any]: "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", - "motion_poll_default_backend": "fast", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_projection_name_order_first": "last_name", "motion_poll_projection_max_columns": 6, "users_enable_presence_view": False, @@ -109,9 +107,8 @@ def get_meeting_defaults(self) -> dict[str, Any]: "assignment_poll_default_type": "pseudoanonymous", "assignment_poll_default_method": "Y", "assignment_poll_default_onehundred_percent_base": "valid", - "assignment_poll_default_backend": "fast", "poll_default_type": "analog", - "poll_default_onehundred_percent_base": "YNA", + "poll_default_onehundred_percent_base": "valid", "poll_default_live_voting_enabled": False, "poll_default_allow_invalid": False, "poll_couple_countdown": True, @@ -507,11 +504,13 @@ def test_correct_relations(self) -> None: "meeting_id": 1, "content_object_id": "motion/1", "visibility": Poll.VISIBILITY_NAMED, - "config_id": "poll_config_rating_approval/1", + "config_id": "poll_config_rating_approval/7", "state": Poll.STATE_STARTED, }, - "poll_config_rating_approval/7": {"poll_id": 7}, - "ballot/8": { + "poll_config_rating_approval/7": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + "poll_ballot/8": { "poll_id": 7, "acting_meeting_user_id": 14, "represented_meeting_user_id": 15, diff --git a/tests/system/presenter/test_check_database_all.py b/tests/system/presenter/test_check_database_all.py index e3e3be4e09..e5ede36915 100644 --- a/tests/system/presenter/test_check_database_all.py +++ b/tests/system/presenter/test_check_database_all.py @@ -92,9 +92,7 @@ def get_meeting_defaults(self) -> dict[str, Any]: "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", - "motion_poll_default_backend": "fast", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_projection_name_order_first": "last_name", "motion_poll_projection_max_columns": 6, "users_enable_presence_view": False, @@ -116,9 +114,8 @@ def get_meeting_defaults(self) -> dict[str, Any]: "assignment_poll_default_type": "pseudoanonymous", "assignment_poll_default_method": "Y", "assignment_poll_default_onehundred_percent_base": "valid", - "assignment_poll_default_backend": "fast", "poll_default_type": "analog", - "poll_default_onehundred_percent_base": "YNA", + "poll_default_onehundred_percent_base": "valid", "poll_default_live_voting_enabled": False, "poll_default_allow_invalid": False, "poll_couple_countdown": True, @@ -583,11 +580,13 @@ def test_correct_relations(self) -> None: "meeting_id": 1, "content_object_id": "motion/1", "visibility": Poll.VISIBILITY_NAMED, - "config_id": "poll_config_rating_approval/1", + "config_id": "poll_config_rating_approval/7", "state": Poll.STATE_STARTED, }, - "poll_config_rating_approval/7": {"poll_id": 7}, - "ballot/8": { + "poll_config_rating_approval/7": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID + }, + "poll_ballot/8": { "poll_id": 7, "acting_meeting_user_id": 14, "represented_meeting_user_id": 15, diff --git a/tests/system/presenter/test_export_meeting.py b/tests/system/presenter/test_export_meeting.py index 27a99a5418..80ad98d989 100644 --- a/tests/system/presenter/test_export_meeting.py +++ b/tests/system/presenter/test_export_meeting.py @@ -61,7 +61,6 @@ def test_correct(self) -> None: "motion_block", "motion_change_recommendation", "poll", - "vote", "assignment", "assignment_candidate", "mediafile", @@ -482,9 +481,7 @@ def test_with_structured_published_orga_files(self) -> None: "motion_poll_ballot_paper_selection": "CUSTOM_NUMBER", "motion_poll_ballot_paper_number": 8, "motion_poll_default_type": "pseudoanonymous", - "motion_poll_default_method": "YNA", - "motion_poll_default_onehundred_percent_base": "YNA", - "motion_poll_default_backend": "fast", + "motion_poll_default_onehundred_percent_base": "valid", "motion_poll_projection_name_order_first": "last_name", "motion_poll_projection_max_columns": 6, "users_enable_presence_view": False, @@ -505,10 +502,10 @@ def test_with_structured_published_orga_files(self) -> None: "assignment_poll_default_type": "pseudoanonymous", "assignment_poll_default_method": "Y", "assignment_poll_default_onehundred_percent_base": "valid", - "assignment_poll_default_backend": "fast", "poll_default_type": "analog", - "poll_default_onehundred_percent_base": "YNA", + "poll_default_onehundred_percent_base": "valid", "poll_default_live_voting_enabled": False, + "poll_default_method": "YNA", "poll_couple_countdown": True, }, "group/1": { From 28f5f78fe013f41862339c0cddcf2f7d5b3c45f2 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Mon, 8 Jun 2026 08:41:09 +0200 Subject: [PATCH 09/12] Complete meeting.import tests --- tests/system/action/meeting/test_import.py | 168 +++++++++++++-------- 1 file changed, 102 insertions(+), 66 deletions(-) diff --git a/tests/system/action/meeting/test_import.py b/tests/system/action/meeting/test_import.py index 019d241692..b8c371ffe5 100644 --- a/tests/system/action/meeting/test_import.py +++ b/tests/system/action/meeting/test_import.py @@ -317,7 +317,11 @@ def create_request_data( if collection not in data["meeting"]: data["meeting"][collection] = models else: - data["meeting"][collection].update(models) + for id_, model in models.items(): + if id_ in data["meeting"][collection]: + data["meeting"][collection][id_].update(model) + else: + data["meeting"][collection][id_] = model return data @@ -429,6 +433,44 @@ def get_meeting_mediafile_data( **data, } + def get_assignment_poll_data(self, obj_id: int = 1) -> dict[str, Any]: + return { + "assignment": { + str(obj_id): { + "id": obj_id, + "title": "just do it", + "meeting_id": 1, + "list_of_speakers_id": obj_id + 100, + "poll_ids": [obj_id], + } + }, + "list_of_speakers": { + str(obj_id + 100): { + "id": obj_id + 100, + "content_object_id": f"assignment/{obj_id}", + "meeting_id": 1, + } + }, + "poll": { + str(obj_id): { + "id": obj_id, + "title": "pull", + "config_id": f"poll_config_approval/{obj_id}", + "visibility": Poll.VISIBILITY_MANUALLY, + "state": Poll.STATE_STARTED, + "meeting_id": 1, + "content_object_id": f"assignment/{obj_id}", + } + }, + "poll_config_approval": { + str(obj_id): { + "id": obj_id, + "poll_id": obj_id, + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, + } + }, + } + def replace_migrated_projector_fields(self, data: dict[str, Any]) -> None: data["meeting"]["meeting"]["1"][ "default_projector_current_list_of_speakers_ids" @@ -2312,8 +2354,39 @@ def test_import_with_wrong_decimal(self) -> None: in response.json["message"] ) - def test_import_new_user_with_vote(self) -> None: + def prepare_user_with_ballot_data(self) -> dict[str, dict[str, Any]]: + data = self.create_request_data( + { + **self.get_assignment_poll_data(), + "poll_ballot": { + "1": { + "id": 1, + "acting_meeting_user_id": 11, + "represented_meeting_user_id": 11, + "poll_id": 1, + }, + }, + "meeting": { + "1": { + "assignment_ids": [1], + "list_of_speakers_ids": [101], + "poll_ids": [1], + } + }, + "meeting_user": { + "11": { + "acting_ballot_ids": [1], + "represented_ballot_ids": [1], + } + }, + } + ) + data["meeting"]["poll"]["1"]["ballot_ids"] = [1] + return data + + def test_import_new_user_with_ballot(self) -> None: self.set_user_groups(1, [1]) + self.create_motion(1, 30) self.set_models( { "poll/1": { @@ -2322,50 +2395,41 @@ def test_import_new_user_with_vote(self) -> None: "visibility": Poll.VISIBILITY_MANUALLY, "state": Poll.STATE_STARTED, "meeting_id": 1, - "content_object_id": "meeting/1", + "content_object_id": "motion/30", + }, + "poll_config_approval/1": { + "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, }, - "poll_config_approval/1": {"poll_id": 1}, - "ballot/1": { + "poll_ballot/1": { "acting_meeting_user_id": 1, "represented_meeting_user_id": 1, "poll_id": 1, }, } ) - data = self.create_request_data( - { - "poll_ballot": { - "1": { - "id": 1, - "acting_meeting_user_id": 1, - "represented_meeting_user_id": 1, - "poll_id": 1, - }, - }, - } - ) - data["meeting"]["meeting_user"]["1"]["acting_ballot_ids"] = [1] - data["meeting"]["meeting_user"]["1"]["represented_ballot_ids"] = [1] + + data = self.prepare_user_with_ballot_data() response = self.request("meeting.import", data) self.assert_status_code(response, 200) self.assert_model_exists( "user/1", { "username": "admin", - "meeting_user_ids": [2], + "meeting_user_ids": [1, 3], }, ) self.assert_model_exists( "user/2", { "username": "test", - "meeting_user_ids": [1], + "meeting_user_ids": [2], }, ) self.assert_model_exists( "meeting_user/1", { - "user_id": 2, + "user_id": 1, + "meeting_id": 1, "acting_ballot_ids": [1], "represented_ballot_ids": [1], }, @@ -2373,11 +2437,21 @@ def test_import_new_user_with_vote(self) -> None: self.assert_model_exists( "meeting_user/2", { - "user_id": 1, + "user_id": 2, + "meeting_id": 2, "acting_ballot_ids": [2], "represented_ballot_ids": [2], }, ) + self.assert_model_exists( + "meeting_user/3", + { + "user_id": 1, + "meeting_id": 2, + "acting_ballot_ids": None, + "represented_ballot_ids": None, + }, + ) def test_gender_import(self) -> None: """ @@ -2516,57 +2590,19 @@ def test_gender_import(self) -> None: {"user_ids": [1, 2, 3, 4, 5, 6, 7], "gender_ids": [1, 4, 5, 6]}, ) - def test_import_existing_user_with_vote(self) -> None: - self.set_user_groups(1, [1]) - self.set_models( - { - "poll/1": { - "title": "pull", - "config_id": "poll_config_approval/1", - "visibility": Poll.VISIBILITY_MANUALLY, - "state": Poll.STATE_STARTED, - "meeting_id": 1, - "content_object_id": "meeting/1", - }, - "poll_config_approval/1": {"poll_id": 1}, - "vote/1": { - "acting_meeting_user_id": 1, - "represented_meeting_user_id": 1, - "poll_id": 1, - }, - } - ) - data = self.create_request_data( - { - "poll_ballot": { - "1": { - "id": 1, - "acting_meeting_user_id": 1, - "represented_meeting_user_id": 1, - }, - }, - } - ) + def test_import_existing_user_with_ballot(self) -> None: + data = self.prepare_user_with_ballot_data() data["meeting"]["user"]["1"]["username"] = "admin" data["meeting"]["user"]["1"]["last_name"] = "" - data["meeting"]["meeting_user"]["1"]["acting_ballot_ids"] = [1] - data["meeting"]["meeting_user"]["1"]["represented_ballot_ids"] = [1] response = self.request("meeting.import", data) self.assert_status_code(response, 200) self.assert_model_exists( "user/1", - { - "username": "admin", - "meeting_user_ids": [1], - }, + {"username": "admin", "meeting_user_ids": [1]}, ) self.assert_model_exists( - "meeting_user_ids/1", - { - "user_id": 1, - "acting_ballot_ids": [1, 2], - "represented_ballot_ids": [1, 2], - }, + "meeting_user/1", + {"user_id": 1, "acting_ballot_ids": [1], "represented_ballot_ids": [1]}, ) self.assert_model_not_exists("user/2") From 64f7bf2293e1552cb56b5a855ade608f12284f56 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Mon, 8 Jun 2026 13:32:36 +0200 Subject: [PATCH 10/12] Revert "Decrease coverage level" This reverts commit 0c0ae78e1a53dc63fbdc536b4d319c2dd9d3aaba. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bc3405e198..1541e42be2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ exclude_lines = raise NotImplementedError @abstractmethod ^\s*...$ -fail_under = 93 +fail_under = 96 omit = tests/* migrations/* From dee9bc96b916f257b47e506c6d83365908a8eea7 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Mon, 8 Jun 2026 13:46:14 +0200 Subject: [PATCH 11/12] Simplify get_assignment_poll_data() --- tests/system/action/meeting/test_import.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/system/action/meeting/test_import.py b/tests/system/action/meeting/test_import.py index b8c371ffe5..03d7330db9 100644 --- a/tests/system/action/meeting/test_import.py +++ b/tests/system/action/meeting/test_import.py @@ -440,13 +440,13 @@ def get_assignment_poll_data(self, obj_id: int = 1) -> dict[str, Any]: "id": obj_id, "title": "just do it", "meeting_id": 1, - "list_of_speakers_id": obj_id + 100, + "list_of_speakers_id": obj_id, "poll_ids": [obj_id], } }, "list_of_speakers": { - str(obj_id + 100): { - "id": obj_id + 100, + str(obj_id): { + "id": obj_id, "content_object_id": f"assignment/{obj_id}", "meeting_id": 1, } @@ -2369,7 +2369,7 @@ def prepare_user_with_ballot_data(self) -> dict[str, dict[str, Any]]: "meeting": { "1": { "assignment_ids": [1], - "list_of_speakers_ids": [101], + "list_of_speakers_ids": [1], "poll_ids": [1], } }, From 4c354a5e909377ab4cd9544b1827f1b66f98e897 Mon Sep 17 00:00:00 2001 From: vkrasnovyd Date: Mon, 8 Jun 2026 15:44:03 +0200 Subject: [PATCH 12/12] Update get_assignment_poll_data() --- tests/system/action/meeting/test_import.py | 92 ++++++++++++++-------- 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/tests/system/action/meeting/test_import.py b/tests/system/action/meeting/test_import.py index 03d7330db9..bd79164c9b 100644 --- a/tests/system/action/meeting/test_import.py +++ b/tests/system/action/meeting/test_import.py @@ -433,42 +433,42 @@ def get_meeting_mediafile_data( **data, } - def get_assignment_poll_data(self, obj_id: int = 1) -> dict[str, Any]: + def get_assignment_poll_data(self, obj_id: int) -> dict[str, Any]: return { - "assignment": { - str(obj_id): { - "id": obj_id, - "title": "just do it", - "meeting_id": 1, - "list_of_speakers_id": obj_id, - "poll_ids": [obj_id], - } - }, - "list_of_speakers": { - str(obj_id): { - "id": obj_id, - "content_object_id": f"assignment/{obj_id}", - "meeting_id": 1, - } - }, "poll": { str(obj_id): { "id": obj_id, "title": "pull", - "config_id": f"poll_config_approval/{obj_id}", + "config_id": f"poll_config_rating_approval/{obj_id + 1}", "visibility": Poll.VISIBILITY_MANUALLY, "state": Poll.STATE_STARTED, "meeting_id": 1, - "content_object_id": f"assignment/{obj_id}", + "content_object_id": f"assignment/{obj_id + 2}", } }, - "poll_config_approval": { - str(obj_id): { - "id": obj_id, + "poll_config_rating_approval": { + str(obj_id + 1): { + "id": obj_id + 1, "poll_id": obj_id, "onehundred_percent_base": Poll.ONEHUNDRED_PERCENT_BASE_VALID, } }, + "assignment": { + str(obj_id + 2): { + "id": obj_id + 2, + "title": "just do it", + "meeting_id": 1, + "list_of_speakers_id": obj_id + 3, + "poll_ids": [obj_id], + } + }, + "list_of_speakers": { + str(obj_id + 3): { + "id": obj_id + 3, + "content_object_id": f"assignment/{obj_id + 2}", + "meeting_id": 1, + } + }, } def replace_migrated_projector_fields(self, data: dict[str, Any]) -> None: @@ -2357,31 +2357,43 @@ def test_import_with_wrong_decimal(self) -> None: def prepare_user_with_ballot_data(self) -> dict[str, dict[str, Any]]: data = self.create_request_data( { - **self.get_assignment_poll_data(), + **self.get_assignment_poll_data(3), "poll_ballot": { - "1": { - "id": 1, + "7": { + "id": 7, + "poll_id": 3, "acting_meeting_user_id": 11, "represented_meeting_user_id": 11, - "poll_id": 1, }, }, "meeting": { "1": { - "assignment_ids": [1], - "list_of_speakers_ids": [1], - "poll_ids": [1], + "poll_ids": [3], + "assignment_ids": [5], + "list_of_speakers_ids": [6], + } + }, + "group": { + "2": { + "poll_ids": [3], } }, "meeting_user": { "11": { - "acting_ballot_ids": [1], - "represented_ballot_ids": [1], + "poll_voted_ids": [3], + "acting_ballot_ids": [7], + "represented_ballot_ids": [7], } }, } ) - data["meeting"]["poll"]["1"]["ballot_ids"] = [1] + data["meeting"]["poll"]["3"].update( + { + "entitled_group_ids": [2], + "ballot_ids": [7], + "voted_ids": [11], + } + ) return data def test_import_new_user_with_ballot(self) -> None: @@ -2405,6 +2417,7 @@ def test_import_new_user_with_ballot(self) -> None: "represented_meeting_user_id": 1, "poll_id": 1, }, + "meeting_user/1": {"poll_voted_ids": [1]}, } ) @@ -2432,6 +2445,7 @@ def test_import_new_user_with_ballot(self) -> None: "meeting_id": 1, "acting_ballot_ids": [1], "represented_ballot_ids": [1], + "poll_voted_ids": [1], }, ) self.assert_model_exists( @@ -2441,6 +2455,7 @@ def test_import_new_user_with_ballot(self) -> None: "meeting_id": 2, "acting_ballot_ids": [2], "represented_ballot_ids": [2], + "poll_voted_ids": [2], }, ) self.assert_model_exists( @@ -2450,8 +2465,21 @@ def test_import_new_user_with_ballot(self) -> None: "meeting_id": 2, "acting_ballot_ids": None, "represented_ballot_ids": None, + "poll_voted_ids": None, + }, + ) + self.assert_model_exists( + "poll/2", + { + "config_id": "poll_config_rating_approval/1", + "content_object_id": "assignment/1", + "meeting_id": 2, + "ballot_ids": [2], + "voted_ids": [2], + "entitled_group_ids": [5], }, ) + self.assert_model_exists("poll_config_rating_approval/1", {"poll_id": 2}) def test_gender_import(self) -> None: """