diff --git a/.eslintrc.json b/.eslintrc.json index 80a8f10ab7364..2bff9292423da 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -31,7 +31,8 @@ "SVGInjector": false, "require": false, "DecidimAdmin": false, - "L": false + "L": false, + "decidimBulletinBoard": false }, "rules": { "accessor-pairs": "error", diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3a06e5f7de7dc..9b735bb9a522e 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -15,7 +15,7 @@ Individual workflows with changes: - run: bundle install --path vendor/bundle --jobs 4 --retry 3 name: Install Ruby deps - run: cp -R vendor/bundle decidim-generators -- run: bundle exec rake +- run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} ``` diff --git a/.github/workflows/ci_accountability.yml b/.github/workflows/ci_accountability.yml index 16b4dc097aa4f..e0229d259e12c 100644 --- a/.github/workflows/ci_accountability.yml +++ b/.github/workflows/ci_accountability.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_admin.yml b/.github/workflows/ci_admin.yml index f917e2a1994e1..71612d649a4c5 100644 --- a/.github/workflows/ci_admin.yml +++ b/.github/workflows/ci_admin.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_api.yml b/.github/workflows/ci_api.yml index 67129ae7ca400..137181f070dc1 100644 --- a/.github/workflows/ci_api.yml +++ b/.github/workflows/ci_api.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_assemblies.yml b/.github/workflows/ci_assemblies.yml index 955b052b8fc3b..81bf353df095a 100644 --- a/.github/workflows/ci_assemblies.yml +++ b/.github/workflows/ci_assemblies.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_blogs.yml b/.github/workflows/ci_blogs.yml index 71274b6450355..79160770a1d67 100644 --- a/.github/workflows/ci_blogs.yml +++ b/.github/workflows/ci_blogs.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_budgets.yml b/.github/workflows/ci_budgets.yml index 41beebe44e7ee..29f778983cc47 100644 --- a/.github/workflows/ci_budgets.yml +++ b/.github/workflows/ci_budgets.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_comments.yml b/.github/workflows/ci_comments.yml index 1668e608c39d8..e3bda73b518ca 100644 --- a/.github/workflows/ci_comments.yml +++ b/.github/workflows/ci_comments.yml @@ -62,7 +62,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_conferences.yml b/.github/workflows/ci_conferences.yml index 0d97790e03f04..5f53d81ed5155 100644 --- a/.github/workflows/ci_conferences.yml +++ b/.github/workflows/ci_conferences.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_consultations.yml b/.github/workflows/ci_consultations.yml index c0fabf0c03299..7da086bbd0845 100644 --- a/.github/workflows/ci_consultations.yml +++ b/.github/workflows/ci_consultations.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_debates.yml b/.github/workflows/ci_debates.yml index ba3e32192348c..8b6d28df5f2ee 100644 --- a/.github/workflows/ci_debates.yml +++ b/.github/workflows/ci_debates.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_elections.yml b/.github/workflows/ci_elections.yml index 0978bed1289f7..46997250de9c1 100644 --- a/.github/workflows/ci_elections.yml +++ b/.github/workflows/ci_elections.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_forms.yml b/.github/workflows/ci_forms.yml index 0255a4303c231..187280a5fe49e 100644 --- a/.github/workflows/ci_forms.yml +++ b/.github/workflows/ci_forms.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_generators.yml b/.github/workflows/ci_generators.yml index 0df6b87c71006..db3f10ce778e4 100644 --- a/.github/workflows/ci_generators.yml +++ b/.github/workflows/ci_generators.yml @@ -52,7 +52,7 @@ jobs: - run: bundle install --jobs 4 --retry 3 name: Install Ruby deps working-directory: ${{ env.DECIDIM_MODULE }} - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_initiatives.yml b/.github/workflows/ci_initiatives.yml index 57744a70d31bc..37499ef2fdf76 100644 --- a/.github/workflows/ci_initiatives.yml +++ b/.github/workflows/ci_initiatives.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_pages.yml b/.github/workflows/ci_pages.yml index d489f49746d12..8819787f68f11 100644 --- a/.github/workflows/ci_pages.yml +++ b/.github/workflows/ci_pages.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_participatory_processes.yml b/.github/workflows/ci_participatory_processes.yml index e02ba455c4006..d82831e46fe81 100644 --- a/.github/workflows/ci_participatory_processes.yml +++ b/.github/workflows/ci_participatory_processes.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_sortitions.yml b/.github/workflows/ci_sortitions.yml index b5ec76269c75e..bd43f646cdd46 100644 --- a/.github/workflows/ci_sortitions.yml +++ b/.github/workflows/ci_sortitions.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_surveys.yml b/.github/workflows/ci_surveys.yml index 62de6b2f8d1eb..3e7c86ba405e4 100644 --- a/.github/workflows/ci_surveys.yml +++ b/.github/workflows/ci_surveys.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_system.yml b/.github/workflows/ci_system.yml index a9b91e3f33242..62679b0a6d067 100644 --- a/.github/workflows/ci_system.yml +++ b/.github/workflows/ci_system.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_templates.yml b/.github/workflows/ci_templates.yml index 9a2eb874b9e39..91a76a0e1a10e 100644 --- a/.github/workflows/ci_templates.yml +++ b/.github/workflows/ci_templates.yml @@ -68,7 +68,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/.github/workflows/ci_verifications.yml b/.github/workflows/ci_verifications.yml index bd30024b718f8..94acb24495880 100644 --- a/.github/workflows/ci_verifications.yml +++ b/.github/workflows/ci_verifications.yml @@ -54,7 +54,7 @@ jobs: - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v1.0.1 - - run: bundle exec rake + - run: bundle exec rspec name: RSpec working-directory: ${{ env.DECIDIM_MODULE }} - run: ./.github/upload_coverage.sh $DECIDIM_MODULE $GITHUB_EVENT_PATH diff --git a/Gemfile.lock b/Gemfile.lock index 7f50a9f294137..7eed322085780 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,6 +142,7 @@ PATH i18n-tasks (~> 0.9.18) mdl (~> 0.5) nokogiri (>= 1.10.8) + puffing-billy (~> 2.4.0) puma (>= 4.3) rails-controller-testing (~> 1.0) rspec-cells (~> 0.3.4) @@ -160,7 +161,7 @@ PATH webmock (~> 3.6) wisper-rspec (~> 1.0) decidim-elections (0.24.0.dev) - decidim-bulletin_board (= 0.2.0) + decidim-bulletin_board (= 0.6.1) decidim-core (= 0.24.0.dev) decidim-forms (= 0.24.0.dev) decidim-proposals (= 0.24.0.dev) @@ -308,13 +309,13 @@ GEM browser (2.7.1) builder (3.2.4) byebug (11.1.3) - capybara (3.33.0) + capybara (3.35.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (2.1.0) activemodel (>= 5.0.0) @@ -335,7 +336,7 @@ GEM actionpack (>= 3.0) cells (>= 4.1.6, < 5.0.0) charlock_holmes (0.7.7) - chef-utils (16.6.14) + chef-utils (16.9.29) childprocess (3.0.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) @@ -348,7 +349,9 @@ GEM coffee-script-source (1.12.2) colorize (0.8.1) concurrent-ruby (1.1.7) - crack (0.4.4) + cookiejar (0.3.3) + crack (0.4.5) + rexml crass (1.0.6) css_parser (1.8.0) addressable @@ -358,12 +361,11 @@ GEM db-query-matchers (0.9.0) activesupport (>= 4.0, <= 6.0) rspec (~> 3.0) - decidim-bulletin_board (0.2.0) - activemodel (~> 5.0, >= 5.0.0.1) - activesupport (~> 5.0, >= 5.0.0.1) + decidim-bulletin_board (0.6.1) byebug (~> 11.0) graphlient (~> 0.4.0) - jwt + jwt (~> 2.2.2) + rails (>= 5.0.0) wisper (~> 2.0.0) declarative-builder (0.1.0) declarative-option (< 0.2.0) @@ -392,20 +394,32 @@ GEM doorkeeper (5.4.0) railties (>= 5) doorkeeper-i18n (4.0.1) + em-http-request (1.1.7) + addressable (>= 2.3.4) + cookiejar (!= 0.3.1) + em-socksify (>= 0.3) + eventmachine (>= 1.0.3) + http_parser.rb (>= 0.6.0) + em-socksify (0.3.2) + eventmachine (>= 1.0.0.beta.4) + em-synchrony (1.0.6) + eventmachine (>= 1.0.0.beta.1) equalizer (0.0.11) - erb_lint (0.0.35) + erb_lint (0.0.37) activesupport better_html (~> 1.0.7) html_tokenizer parser (>= 2.7.1.4) rainbow - rubocop (~> 0.79) + rubocop smart_properties erbse (0.1.4) temple erubi (1.9.0) etherpad-lite (0.3.0) rest-client (>= 1.6) + eventmachine (1.2.7) + eventmachine_httpserver (0.2.1) excon (0.78.1) execjs (2.7.0) factory_bot (4.11.1) @@ -463,9 +477,10 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) + http_parser.rb (0.6.0) i18n (1.8.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.31) + i18n-tasks (0.9.33) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -543,7 +558,7 @@ GEM mixlib-cli (2.1.8) mixlib-config (3.0.9) tomlrb - mixlib-shellout (3.1.7) + mixlib-shellout (3.2.2) chef-utils msgpack (1.3.3) multi_json (1.15.0) @@ -606,6 +621,14 @@ GEM actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) public_suffix (4.0.6) + puffing-billy (2.4.1) + addressable (~> 2.5) + em-http-request (~> 1.1, >= 1.1.0) + em-synchrony + eventmachine (~> 1.2) + eventmachine_httpserver + http_parser.rb (~> 0.6.0) + multi_json puma (5.0.0) nio4r (~> 2.0) racc (1.5.2) @@ -723,10 +746,10 @@ GEM rubocop-faker (1.1.0) faker (>= 2.12.0) rubocop (>= 0.82.0) - rubocop-rails (2.8.1) + rubocop-rails (2.9.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.87.0) + rubocop (>= 0.90.0, < 2.0) rubocop-rspec (1.43.2) rubocop (~> 0.87) ruby-ole (1.2.12.2) @@ -787,7 +810,7 @@ GEM thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - tomlrb (1.3.0) + tomlrb (2.0.1) truncato (0.7.11) htmlentities (~> 4.3.1) nokogiri (>= 1.7.0, <= 2.0) @@ -818,7 +841,7 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webmock (3.9.3) + webmock (3.11.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 0f61464c1603f..ea549c6a04d3e 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -188,6 +188,11 @@ ignore_unused: - decidim.consultations.admin_log.* - decidim.consultations.consultations.orders.* - decidim.consultations.show.badge_name.* + - decidim.votings.admin_log.* + - decidim.votings.votings.filters.* + - decidim.votings.votings.orders.* + - decidim.votings.votings_m.badge_name.* + - decidim.votings.votings_m.footer_button_text.* - decidim.initiatives.admin_log.* - decidim.initiatives.initiatives.orders.* - decidim.initiatives.states.* @@ -216,6 +221,7 @@ ignore_unused: - decidim.initiatives.content_blocks.*.name - decidim.consultations.content_blocks.*.name - decidim.meetings.content_blocks.*.name + - decidim.votings.content_blocks.*.name - decidim.proposals.collaborative_drafts.show.hidden_authors_count.* - decidim.proposals.collaborative_drafts.orders* - decidim.proposals.collaborative_drafts.filters* @@ -288,6 +294,7 @@ ignore_unused: - decidim.debates.debate_m.commented_time_ago - decidim.elections.trustee_zone.trustees.show.identification_keys.upload_error.* - decidim.elections.feedback.answer.* + - decidim.elections.trustee_zone.elections.key_ceremony_steps.keys.* - decidim.votings.admin_log.* diff --git a/decidim-api/lib/decidim/api/schema.rb b/decidim-api/lib/decidim/api/schema.rb index aaa488fbf5a6e..b1d0a4d09d211 100644 --- a/decidim-api/lib/decidim/api/schema.rb +++ b/decidim-api/lib/decidim/api/schema.rb @@ -12,12 +12,6 @@ class Schema < GraphQL::Schema max_complexity 300 orphan_types(Api.orphan_types) - # Opt in to the new runtime (default in future graphql-ruby versions) - use GraphQL::Execution::Interpreter - use GraphQL::Analysis::AST - - # Add built-in connections for pagination - use GraphQL::Pagination::Connections end end end diff --git a/decidim-assemblies/lib/decidim/assemblies/query_extensions.rb b/decidim-assemblies/lib/decidim/assemblies/query_extensions.rb index cf4430936a752..e71276a243b76 100644 --- a/decidim-assemblies/lib/decidim/assemblies/query_extensions.rb +++ b/decidim-assemblies/lib/decidim/assemblies/query_extensions.rb @@ -47,12 +47,12 @@ def assemblies_type(id:) def assemblies(filter: {}, order: {}) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :assemblies }.first - Decidim::Core::ParticipatorySpaceList.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) end def assembly(id: nil) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :assemblies }.first - Decidim::Core::ParticipatorySpaceFinder.new(manifest: manifest).call(object, { id: id }, context) + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id }, context) end end end diff --git a/decidim-comments/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb b/decidim-comments/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb index 4bd72aefaa4df..8008065fd606c 100644 --- a/decidim-comments/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb +++ b/decidim-comments/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb @@ -41,8 +41,8 @@ def retrieve_comments_for_organization user_ids = Decidim::User.where(organization: @resource.organization).pluck(:id) Decidim::Comments::Comment.includes(:root_commentable).not_hidden .where("decidim_comments_comments.created_at <= ?", end_time) - .where("decidim_comments_comments.decidim_author_id IN (?)", user_ids) - .where("decidim_comments_comments.decidim_author_type IN (?)", "Decidim::UserBaseEntity") + .where(decidim_comments_comments: { decidim_author_id: user_ids }) + .where(decidim_comments_comments: { decidim_author_type: "Decidim::UserBaseEntity" }) end end end diff --git a/decidim-conferences/lib/decidim/conferences/query_extensions.rb b/decidim-conferences/lib/decidim/conferences/query_extensions.rb index de2690c79b518..fb7ee41148aab 100644 --- a/decidim-conferences/lib/decidim/conferences/query_extensions.rb +++ b/decidim-conferences/lib/decidim/conferences/query_extensions.rb @@ -30,13 +30,13 @@ def self.included(type) def conferences(filter: {}, order: {}) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :conferences }.first - Decidim::Core::ParticipatorySpaceList.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) end def conference(id: nil) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :conferences }.first - Decidim::Core::ParticipatorySpaceFinder.new(manifest: manifest).call(object, { id: id }, context) + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id }, context) end end end diff --git a/decidim-consultations/app/views/decidim/consultations/consultations/_consultation_details.html.erb b/decidim-consultations/app/views/decidim/consultations/consultations/_consultation_details.html.erb index 1e22d76f438a3..308db03c443e8 100644 --- a/decidim-consultations/app/views/decidim/consultations/consultations/_consultation_details.html.erb +++ b/decidim-consultations/app/views/decidim/consultations/consultations/_consultation_details.html.erb @@ -1,6 +1,6 @@
-

<%= decidim_sanitize translated_attribute(consultation.description), strip_tags: true %>

+

<%= decidim_sanitize translated_attribute(consultation.description) %>

<% if consultation.introductory_video_url.blank? %> diff --git a/decidim-consultations/lib/decidim/consultations/engine.rb b/decidim-consultations/lib/decidim/consultations/engine.rb index fae86b73ec9b2..5f4cd3afa0a50 100644 --- a/decidim-consultations/lib/decidim/consultations/engine.rb +++ b/decidim-consultations/lib/decidim/consultations/engine.rb @@ -74,7 +74,7 @@ class Engine < ::Rails::Engine Decidim.menu :menu do |menu| menu.item I18n.t("menu.consultations", scope: "decidim"), decidim_consultations.consultations_path, - position: 2.7, + position: 2.8, if: Decidim::Consultation.where(organization: current_organization).published.any?, active: :inclusive end diff --git a/decidim-consultations/lib/decidim/consultations/query_extensions.rb b/decidim-consultations/lib/decidim/consultations/query_extensions.rb index fed3122aeb64e..afb7f84af7fa7 100644 --- a/decidim-consultations/lib/decidim/consultations/query_extensions.rb +++ b/decidim-consultations/lib/decidim/consultations/query_extensions.rb @@ -30,13 +30,13 @@ def self.included(type) def consultations(filter: {}, order: {}) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :consultations }.first - Decidim::Core::ParticipatorySpaceList.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) end def consultation(id: nil) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :consultations }.first - Decidim::Core::ParticipatorySpaceFinder.new(manifest: manifest).call(object, { id: id }, context) + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id }, context) end end end diff --git a/decidim-core/app/commands/decidim/multiple_attachments_methods.rb b/decidim-core/app/commands/decidim/multiple_attachments_methods.rb index 39f49a54e239c..765f0255120f8 100644 --- a/decidim-core/app/commands/decidim/multiple_attachments_methods.rb +++ b/decidim-core/app/commands/decidim/multiple_attachments_methods.rb @@ -17,11 +17,15 @@ def build_attachments def attachments_invalid? @documents.each do |file| - if file.invalid? && file.errors.has_key?(:file) - @form.errors.add(:add_documents, file.errors[:file]) - return true + next if file.valid? || !file.errors.has_key?(:file) + + file.errors[:file].each do |error| + @form.errors.add(:add_documents, error) end + + return true end + false end diff --git a/decidim-core/app/functions/decidim/core/participatory_space_finder.rb b/decidim-core/app/functions/decidim/core/participatory_space_finder.rb deleted file mode 100644 index cf6bcd2107f23..0000000000000 --- a/decidim-core/app/functions/decidim/core/participatory_space_finder.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module Core - # A very basic resolver for the GraphQL endpoint for a single participatory spaces - # This can be easily overwritten by the participatory_space_manifest.query_finder - class ParticipatorySpaceFinder < ParticipatorySpaceFinderBase - argument :id, GraphQL::Types::ID, "The ID of the participatory space" - end - end -end diff --git a/decidim-core/app/functions/decidim/core/participatory_space_list.rb b/decidim-core/app/functions/decidim/core/participatory_space_list.rb deleted file mode 100644 index 6f073d8fcf25f..0000000000000 --- a/decidim-core/app/functions/decidim/core/participatory_space_list.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module Core - # A very basic resolver for the GraphQL endpoint for listing participatory spaces - class ParticipatorySpaceList < ParticipatorySpaceListBase - argument :filter, ParticipatorySpaceInputFilter, "Provides several methods to filter the results" - argument :order, ParticipatorySpaceInputSort, "Provides several methods to order the results" - end - end -end diff --git a/decidim-core/app/models/decidim/attachment.rb b/decidim-core/app/models/decidim/attachment.rb index 1bfa5bedc6adb..ddb9b16e79b17 100644 --- a/decidim-core/app/models/decidim/attachment.rb +++ b/decidim-core/app/models/decidim/attachment.rb @@ -11,10 +11,11 @@ class Attachment < ApplicationRecord belongs_to :attachment_collection, class_name: "Decidim::AttachmentCollection", optional: true belongs_to :attached_to, polymorphic: true - validates :file, :content_type, presence: true - validates_upload :file mount_uploader :file, Decidim::AttachmentUploader + validates_upload :file + validates :file, :content_type, presence: true + default_scope { order(arel_table[:weight].asc, arel_table[:id].asc) } # Returns the organization related to this attachment in case the diff --git a/decidim-core/lib/decidim/amendable.rb b/decidim-core/lib/decidim/amendable.rb index 48eaed4db6349..a8b67d6bfd426 100644 --- a/decidim-core/lib/decidim/amendable.rb +++ b/decidim-core/lib/decidim/amendable.rb @@ -44,7 +44,7 @@ module Amendable when "participants" return none unless user - where(id: joins(:amendable).where("decidim_amendments.decidim_user_id = ?", user.id)) + where(id: joins(:amendable).where(decidim_amendments: { decidim_user_id: user.id })) else # Assume 'all' only_emendations end @@ -135,7 +135,7 @@ def visible_emendations_for(user) when "participants" return self.class.none unless user - published_emendations.where("decidim_amendments.decidim_user_id = ?", user.id) + published_emendations.where(decidim_amendments: { decidim_user_id: user.id }) else # Assume 'all' published_emendations end diff --git a/decidim-core/app/functions/decidim/core/component_finder_base.rb b/decidim-core/lib/decidim/api/functions/component_finder_base.rb similarity index 95% rename from decidim-core/app/functions/decidim/core/component_finder_base.rb rename to decidim-core/lib/decidim/api/functions/component_finder_base.rb index 696a3b5c3d6a6..e21aca0d9f691 100644 --- a/decidim-core/app/functions/decidim/core/component_finder_base.rb +++ b/decidim-core/lib/decidim/api/functions/component_finder_base.rb @@ -14,7 +14,7 @@ module Core # # For an example check # decidim-proposals/app/types/decidim/proposals/proposals_type.rb - class ComponentFinderBase < GraphQL::Function + class ComponentFinderBase attr_reader :model_class def initialize(model_class:) diff --git a/decidim-core/app/functions/decidim/core/component_list.rb b/decidim-core/lib/decidim/api/functions/component_list.rb similarity index 95% rename from decidim-core/app/functions/decidim/core/component_list.rb rename to decidim-core/lib/decidim/api/functions/component_list.rb index 5889b70ed2f42..46dcbf8f4e2ac 100644 --- a/decidim-core/app/functions/decidim/core/component_list.rb +++ b/decidim-core/lib/decidim/api/functions/component_list.rb @@ -14,7 +14,7 @@ module Core # This is used by ParticipatorySpaceInterface to apply filter/orders when # searching raw components. When listing properly defined components, # use ComponentListBase instead - class ComponentList < GraphQL::Function + class ComponentList include NeedsApiFilterAndOrder attr_reader :model_class diff --git a/decidim-core/app/functions/decidim/core/component_list_base.rb b/decidim-core/lib/decidim/api/functions/component_list_base.rb similarity index 97% rename from decidim-core/app/functions/decidim/core/component_list_base.rb rename to decidim-core/lib/decidim/api/functions/component_list_base.rb index 15602ba5c0d38..2c6220cdb20a7 100644 --- a/decidim-core/app/functions/decidim/core/component_list_base.rb +++ b/decidim-core/lib/decidim/api/functions/component_list_base.rb @@ -25,7 +25,7 @@ module Core # For an example check # decidim-proposals/app/types/decidim/proposals/proposals_type.rb # - class ComponentListBase < GraphQL::Function + class ComponentListBase include NeedsApiFilterAndOrder attr_reader :model_class diff --git a/decidim-core/app/functions/decidim/core/needs_api_filter_and_order.rb b/decidim-core/lib/decidim/api/functions/needs_api_filter_and_order.rb similarity index 100% rename from decidim-core/app/functions/decidim/core/needs_api_filter_and_order.rb rename to decidim-core/lib/decidim/api/functions/needs_api_filter_and_order.rb diff --git a/decidim-core/app/functions/decidim/core/participatory_space_finder_base.rb b/decidim-core/lib/decidim/api/functions/participatory_space_finder_base.rb similarity index 93% rename from decidim-core/app/functions/decidim/core/participatory_space_finder_base.rb rename to decidim-core/lib/decidim/api/functions/participatory_space_finder_base.rb index 321ac13b3cc7d..9917d829495bc 100644 --- a/decidim-core/app/functions/decidim/core/participatory_space_finder_base.rb +++ b/decidim-core/lib/decidim/api/functions/participatory_space_finder_base.rb @@ -5,7 +5,7 @@ module Core # An abstract base class resolver for the GraphQL endpoint for a single participatory space # Inherit from this class and add search arguments to create finder participatory classes # as is shown in ParticipatorySpaceFinder - class ParticipatorySpaceFinderBase < GraphQL::Function + class ParticipatorySpaceFinderBase attr_reader :manifest def initialize(manifest:) diff --git a/decidim-core/app/functions/decidim/core/participatory_space_list_base.rb b/decidim-core/lib/decidim/api/functions/participatory_space_list_base.rb similarity index 94% rename from decidim-core/app/functions/decidim/core/participatory_space_list_base.rb rename to decidim-core/lib/decidim/api/functions/participatory_space_list_base.rb index c3c8092945a3f..69ea4b9026cdf 100644 --- a/decidim-core/app/functions/decidim/core/participatory_space_list_base.rb +++ b/decidim-core/lib/decidim/api/functions/participatory_space_list_base.rb @@ -7,7 +7,7 @@ module Core # as is shown in ParticipatorySpaceList # + info: # https://github.com/rmosolgo/graphql-ruby/blob/v1.6.8/guides/fields/function.md - class ParticipatorySpaceListBase < GraphQL::Function + class ParticipatorySpaceListBase include NeedsApiFilterAndOrder attr_reader :manifest diff --git a/decidim-core/app/functions/decidim/core/user_entity_finder.rb b/decidim-core/lib/decidim/api/functions/user_entity_finder.rb similarity index 78% rename from decidim-core/app/functions/decidim/core/user_entity_finder.rb rename to decidim-core/lib/decidim/api/functions/user_entity_finder.rb index 885e65e2b5b8e..2ffb66e436f2d 100644 --- a/decidim-core/app/functions/decidim/core/user_entity_finder.rb +++ b/decidim-core/lib/decidim/api/functions/user_entity_finder.rb @@ -9,10 +9,7 @@ module Core # name # } # - class UserEntityFinder < GraphQL::Function - argument :id, types.ID, "The ID of the participant" - argument :nickname, types.String, "The @nickname of the participant" - + class UserEntityFinder def call(_obj, args, ctx) filters = { organization: ctx[:current_organization] diff --git a/decidim-core/app/functions/decidim/core/user_entity_list.rb b/decidim-core/lib/decidim/api/functions/user_entity_list.rb similarity index 73% rename from decidim-core/app/functions/decidim/core/user_entity_list.rb rename to decidim-core/lib/decidim/api/functions/user_entity_list.rb index 499db3d56e7d8..90f0d789d53d0 100644 --- a/decidim-core/app/functions/decidim/core/user_entity_list.rb +++ b/decidim-core/lib/decidim/api/functions/user_entity_list.rb @@ -9,10 +9,8 @@ module Core # name # } # - class UserEntityList < GraphQL::Function + class UserEntityList include NeedsApiFilterAndOrder - argument :order, UserEntityInputSort, "Provides several methods to order the results" - argument :filter, UserEntityInputFilter, "Provides several methods to filter the results" def initialize @model_class = Decidim::UserBaseEntity diff --git a/decidim-core/lib/decidim/core.rb b/decidim-core/lib/decidim/core.rb index c6c80662b1717..c832ea00c4c0e 100644 --- a/decidim-core/lib/decidim/core.rb +++ b/decidim-core/lib/decidim/core.rb @@ -558,7 +558,7 @@ def self.organization_settings(model) organization = begin if model.is_a?(Decidim::Organization) model - elsif model.respond_to?(:organization) + elsif model.respond_to?(:organization) && model.organization.present? model.organization end end diff --git a/decidim-core/lib/decidim/core/api.rb b/decidim-core/lib/decidim/core/api.rb index a7c595105cb25..f0eecf611ed94 100644 --- a/decidim-core/lib/decidim/core/api.rb +++ b/decidim-core/lib/decidim/core/api.rb @@ -2,6 +2,15 @@ module Decidim module Core + autoload :ComponentFinderBase, "decidim/api/functions/component_finder_base" + autoload :ComponentList, "decidim/api/functions/component_list" + autoload :ComponentListBase, "decidim/api/functions/component_list_base" + autoload :NeedsApiFilterAndOrder, "decidim/api/functions/needs_api_filter_and_order" + autoload :ParticipatorySpaceFinderBase, "decidim/api/functions/participatory_space_finder_base" + autoload :ParticipatorySpaceListBase, "decidim/api/functions/participatory_space_list_base" + autoload :UserEntityFinder, "decidim/api/functions/user_entity_finder" + autoload :UserEntityList, "decidim/api/functions/user_entity_list" + autoload :AmendmentType, "decidim/api/types/amendment_type" autoload :AreaApiType, "decidim/api/types/area_api_type" autoload :AreaTypeType, "decidim/api/types/area_type_type" diff --git a/decidim-core/lib/decidim/has_private_users.rb b/decidim-core/lib/decidim/has_private_users.rb index 4f8ffcf2e97f3..a8abf29c87cd3 100644 --- a/decidim-core/lib/decidim/has_private_users.rb +++ b/decidim-core/lib/decidim/has_private_users.rb @@ -26,7 +26,7 @@ def self.visible_for(user) id: public_spaces + private_spaces .joins(:participatory_space_private_users) - .where("decidim_participatory_space_private_users.decidim_user_id = ?", user.id) + .where(decidim_participatory_space_private_users: { decidim_user_id: user.id }) ) else public_spaces diff --git a/decidim-core/lib/decidim/query_extensions.rb b/decidim-core/lib/decidim/query_extensions.rb index 0cd4616ba92e8..26ed2631f8bfe 100644 --- a/decidim-core/lib/decidim/query_extensions.rb +++ b/decidim-core/lib/decidim/query_extensions.rb @@ -64,12 +64,12 @@ def self.included(type) def participatory_processes(filter: {}, order: {}) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :participatory_processes }.first - Decidim::ParticipatoryProcesses::ParticipatoryProcessList.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) end def participatory_process(id: nil, slug: nil) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :participatory_processes }.first - Decidim::ParticipatoryProcesses::ParticipatoryProcessFinder.new(manifest: manifest).call(object, { id: id, slug: slug }, context) + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id, slug: slug }, context) end def component(id: {}) @@ -112,8 +112,8 @@ def metrics(names: [], space_type: nil, space_id: nil) end end - def user(id: nil) - Core::UserEntityFinder.new.call(object, { id: id }, context) + def user(id: nil, nickname: nil) + Core::UserEntityFinder.new.call(object, { id: id, nickname: nickname }, context) end def users(filter: {}, order: {}) diff --git a/decidim-debates/app/commands/decidim/debates/admin/archive_debate.rb b/decidim-debates/app/commands/decidim/debates/admin/archive_debate.rb deleted file mode 100644 index 39a519c2f79ca..0000000000000 --- a/decidim-debates/app/commands/decidim/debates/admin/archive_debate.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module Debates - module Admin - # A command with all the business logic when an admin archives a debate. - class ArchiveDebate < Rectify::Command - # Public: Initializes the command. - # - # archive - Boolean, whether to archive (true) or unarchive (false) the debate. - # debate - The debate object to archive. - # user - The user performing the action. - def initialize(archive, debate, user) - @archive = archive - @debate = debate - @user = user - end - - # Executes the command. Broadcasts these events: - # - # - :ok when the debate is valid. - # - :invalid if the debate wasn't valid and we couldn't proceed. - # - # Returns nothing. - def call - archive_debate - broadcast(:ok) - rescue ActiveRecord::RecordInvalid - broadcast(:invalid) - end - - attr_reader :debate - - private - - def archive_debate - @debate = Decidim.traceability.perform_action!( - :close, - @debate, - @user - ) do - @debate.update!( - archived_at: @archive ? Time.zone.now : nil - ) - end - end - end - end - end -end diff --git a/decidim-debates/app/commands/decidim/debates/admin/close_debate.rb b/decidim-debates/app/commands/decidim/debates/admin/close_debate.rb index 4c7940b516b4f..8c60fbeaac36a 100644 --- a/decidim-debates/app/commands/decidim/debates/admin/close_debate.rb +++ b/decidim-debates/app/commands/decidim/debates/admin/close_debate.rb @@ -37,8 +37,7 @@ def close_debate ) do form.debate.update!( conclusions: form.conclusions, - closed_at: form.closed_at, - archived_at: (Time.zone.now if form.archive) + closed_at: form.closed_at ) end diff --git a/decidim-debates/app/controllers/decidim/debates/admin/debates_controller.rb b/decidim-debates/app/controllers/decidim/debates/admin/debates_controller.rb index bfe20506c6b38..ba2ae310cefaa 100644 --- a/decidim-debates/app/controllers/decidim/debates/admin/debates_controller.rb +++ b/decidim-debates/app/controllers/decidim/debates/admin/debates_controller.rb @@ -61,24 +61,6 @@ def update end end - def archive - enforce_permission_to :archive, :debate, debate: debate - - archive = params[:archive] == "true" - - ArchiveDebate.call(archive, debate, current_user) do - on(:ok) do - flash[:notice] = I18n.t("debates.#{archive ? "archive" : "unarchive"}.success", scope: "decidim.debates.admin") - redirect_to debates_path(archive ? {} : { filter: "archive" }) - end - - on(:invalid) do - flash.now[:alert] = I18n.t("debates.#{archive ? "archive" : "unarchive"}.invalid", scope: "decidim.debates.admin") - redirect_to debates_path(archive ? {} : { filter: "archive" }) - end - end - end - def destroy enforce_permission_to :delete, :debate, debate: debate @@ -92,19 +74,11 @@ def destroy private def debates - @debates ||= archive? ? all_debates.archived : all_debates.not_archived + @debates ||= Debate.where(component: current_component) end def debate - @debate ||= all_debates.find(params[:id]) - end - - def all_debates - Debate.where(component: current_component) - end - - def archive? - params[:filter] == "archive" + @debate ||= debates.find(params[:id]) end end end diff --git a/decidim-debates/app/forms/decidim/debates/admin/close_debate_form.rb b/decidim-debates/app/forms/decidim/debates/admin/close_debate_form.rb index d400d3ae65a3c..9ce65b8abe8b9 100644 --- a/decidim-debates/app/forms/decidim/debates/admin/close_debate_form.rb +++ b/decidim-debates/app/forms/decidim/debates/admin/close_debate_form.rb @@ -15,7 +15,6 @@ class CloseDebateForm < Decidim::Form end attribute :debate, Debate - attribute :archive, Boolean validates :debate, presence: true validate :user_can_close_debate @@ -24,10 +23,6 @@ def closed_at debate&.closed_at || Time.current end - def map_model(model) - self.archive = model.archived_at.present? - end - private def user_can_close_debate diff --git a/decidim-debates/app/helpers/decidim/debates/admin/application_helper.rb b/decidim-debates/app/helpers/decidim/debates/admin/application_helper.rb index 227311f8ee554..2dbfb7caeab7b 100644 --- a/decidim-debates/app/helpers/decidim/debates/admin/application_helper.rb +++ b/decidim-debates/app/helpers/decidim/debates/admin/application_helper.rb @@ -7,15 +7,6 @@ module Admin # module ApplicationHelper include Decidim::Admin::ResourceScopeHelper - - def link_to_filtered_debates - unfiltered = params[:filter].blank? - link_to( - t("actions.#{unfiltered ? "archived" : "active"}", scope: "decidim.debates", name: t("models.debate.name", scope: "decidim.debates.admin")), - debates_path(unfiltered ? { filter: "archive" } : {}), - class: "button tiny button--simple" - ) - end end end end diff --git a/decidim-debates/app/models/decidim/debates/debate.rb b/decidim-debates/app/models/decidim/debates/debate.rb index eb40eb4147c7a..d2d89b6d100d8 100644 --- a/decidim-debates/app/models/decidim/debates/debate.rb +++ b/decidim-debates/app/models/decidim/debates/debate.rb @@ -42,8 +42,6 @@ class Debate < Debates::ApplicationRecord scope :open, -> { where(closed_at: nil) } scope :closed, -> { where.not(closed_at: nil) } - scope :archived, -> { where.not(archived_at: nil) } - scope :not_archived, -> { where(archived_at: nil) } scope :authored_by, ->(author) { where(author: author) } scope :commented_by, lambda { |author| joins(:comments).where( @@ -98,13 +96,6 @@ def open? (ama? && open_ama?) || !ama? end - # Public: Checks if the debate is archived or not. - # - # Returns a boolean. - def archived? - archived_at.present? - end - # Public: Overrides the `commentable?` Commentable concern method. def commentable? component.settings.comments_enabled? @@ -179,13 +170,6 @@ def closeable_by?(user) authored_by?(user) end - # Checks whether the user can archive the debate. - # - # user - the user to check for authorship - def archivable_by?(user) - authored_by?(user) - end - # Public: Updates the comments counter cache. We have to do it these # way in order to properly calculate the counter with hidden # comments. diff --git a/decidim-debates/app/permissions/decidim/debates/admin/permissions.rb b/decidim-debates/app/permissions/decidim/debates/admin/permissions.rb index c00dc56d70bfc..452d02ffc8613 100644 --- a/decidim-debates/app/permissions/decidim/debates/admin/permissions.rb +++ b/decidim-debates/app/permissions/decidim/debates/admin/permissions.rb @@ -15,8 +15,6 @@ def permissions case permission_action.action when :create, :read, :export allow! - when :archive - toggle_allow(debate.present?) when :update toggle_allow(debate && !debate.closed? && debate.official?) when :delete, :close diff --git a/decidim-debates/app/views/decidim/debates/admin/debate_closes/edit.html.erb b/decidim-debates/app/views/decidim/debates/admin/debate_closes/edit.html.erb index f6744df3607b7..1bcc8c8e68c34 100644 --- a/decidim-debates/app/views/decidim/debates/admin/debate_closes/edit.html.erb +++ b/decidim-debates/app/views/decidim/debates/admin/debate_closes/edit.html.erb @@ -9,12 +9,6 @@ <%= f.translated :editor, :conclusions, autofocus: true, rows: 15 %>
-
-
- <%= f.check_box :archive, label: t(".archive") %> -

<%= t(".archive_help") %>

-
-
diff --git a/decidim-debates/app/views/decidim/debates/admin/debates/index.html.erb b/decidim-debates/app/views/decidim/debates/admin/debates/index.html.erb index c5702733ac3e6..236cdaa18973c 100644 --- a/decidim-debates/app/views/decidim/debates/admin/debates/index.html.erb +++ b/decidim-debates/app/views/decidim/debates/admin/debates/index.html.erb @@ -4,74 +4,63 @@ <%= t(".title") %>
<%= export_dropdown if allowed_to? :export, :comments %> - <%= link_to_filtered_debates if allowed_to? :read, :debate %> <%= link_to t("actions.new", scope: "decidim.debates", name: t("models.debate.name", scope: "decidim.debates.admin")), new_debate_path, class: "button tiny button--simple" if allowed_to? :create, :debate %>
- <% if debates.any? %> -
- - - - - - - <%= th_resource_scope_label %> - - - - - <% debates.each do |debate| %> - - - - - <%= td_resource_scope_for(debate.scope) %> - + + <% end %> + +
<%= t("models.debate.fields.title", scope: "decidim.debates") %><%= t("models.debate.fields.start_time", scope: "decidim.debates") %><%= t("models.debate.fields.end_time", scope: "decidim.debates") %><%= t("actions.title", scope: "decidim.debates") %>
- <%= link_to present(debate).title, resource_locator(debate).path, target: :blank %>
-
- <% if debate.start_time %> - <%= l debate.start_time, format: :long %> - <% end %> - - <% if debate.end_time %> - <%= l debate.end_time, format: :long %> - <% end %> - - <% if allowed_to? :update, :debate, debate: debate %> - <%= icon_link_to "pencil", edit_debate_path(debate), t("actions.edit", scope: "decidim.debates"), class: "action-icon--edit" %> - <% else %> - - <% end %> - - <% if allowed_to? :close, :debate, debate: debate %> - <%= icon_link_to "lock-locked", edit_debate_debate_close_path(debate_id: debate.id, id: debate.id), t("actions.close", scope: "decidim.debates"), class: "action-icon--close #{"action-icon--highlighted" if debate.closed?}" %> - <% else %> - - <% end %> +
+ + + + + + + <%= th_resource_scope_label %> + + + + + <% debates.each do |debate| %> + + + + + <%= td_resource_scope_for(debate.scope) %> + - - <% end %> - -
<%= t("models.debate.fields.title", scope: "decidim.debates") %><%= t("models.debate.fields.start_time", scope: "decidim.debates") %><%= t("models.debate.fields.end_time", scope: "decidim.debates") %><%= t("actions.title", scope: "decidim.debates") %>
+ <%= link_to present(debate).title, resource_locator(debate).path, target: :blank %>
+
+ <% if debate.start_time %> + <%= l debate.start_time, format: :long %> + <% end %> + + <% if debate.end_time %> + <%= l debate.end_time, format: :long %> + <% end %> + + <% if allowed_to? :update, :debate, debate: debate %> + <%= icon_link_to "pencil", edit_debate_path(debate), t("actions.edit", scope: "decidim.debates"), class: "action-icon--edit" %> + <% else %> + + <% end %> - <% if allowed_to? :archive, :debate, debate: debate %> - <%= icon_link_to "folder", archive_debate_path(id: debate.id, archive: !debate.archived?), t("actions.#{ debate.archived? ? "unarchive" : "archive" }", scope: "decidim.debates"), class: "action-icon--archive #{"action-icon--highlighted" if debate.archived?}", method: :post %> - <% else %> - - <% end %> + <% if allowed_to? :close, :debate, debate: debate %> + <%= icon_link_to "lock-locked", edit_debate_debate_close_path(debate_id: debate.id, id: debate.id), t("actions.close", scope: "decidim.debates"), class: "action-icon--close" %> + <% else %> + + <% end %> - <% if allowed_to? :delete, :debate, debate: debate %> - <%= icon_link_to "circle-x", debate_path(debate), t("actions.destroy", scope: "decidim.debates"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.debates") } %> - <% else %> - - <% end %> -
-
- <% else %> - <%= t("debates.empty", scope: "decidim.debates.admin") %> - <% end %> + <% if allowed_to? :delete, :debate, debate: debate %> + <%= icon_link_to "circle-x", debate_path(debate), t("actions.destroy", scope: "decidim.debates"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.debates") } %> + <% else %> + + <% end %> +
+
diff --git a/decidim-debates/config/locales/en.yml b/decidim-debates/config/locales/en.yml index 76aa23dd838aa..6e767d36b694d 100644 --- a/decidim-debates/config/locales/en.yml +++ b/decidim-debates/config/locales/en.yml @@ -46,27 +46,18 @@ en: endorsements_enabled: Endorsements enabled debates: actions: - active: Active debates - archive: Archive - archived: Archived debates close: Close confirm_destroy: Are you sure? destroy: Delete edit: Edit new: New %{name} title: Actions - unarchive: Unarchive admin: debate_closes: edit: - archive: Archive this debate - archive_help: Archiving this debate will hide it from the public debates section close: Close title: Close debate debates: - archive: - invalid: There was a problem archiving the debate. - success: Debate successfully archived. create: invalid: There was a problem creating the debate. success: Debate successfully created. @@ -75,7 +66,6 @@ en: edit: title: Edit debate update: Update debate - empty: There are no debates matching these criteria. form: debate_type: Debate type finite: Finite (With start and end times) @@ -85,9 +75,6 @@ en: new: create: Create debate title: New debate - unarchive: - invalid: There was a problem unarchiving the debate. - success: Debate successfully unarchived. update: invalid: There was a problem updating this debate. success: Debate successfully updated. diff --git a/decidim-debates/db/migrate/20210125101735_revert_archive_debates.rb b/decidim-debates/db/migrate/20210125101735_revert_archive_debates.rb new file mode 100644 index 0000000000000..2b326996b7b69 --- /dev/null +++ b/decidim-debates/db/migrate/20210125101735_revert_archive_debates.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class RevertArchiveDebates < ActiveRecord::Migration[5.2] + def change + remove_index :decidim_debates_debates, :archived_at + remove_column :decidim_debates_debates, :archived_at + end +end diff --git a/decidim-debates/lib/decidim/debates/admin_engine.rb b/decidim-debates/lib/decidim/debates/admin_engine.rb index 6ef20e5c0ff38..62f9d6acb9cc0 100644 --- a/decidim-debates/lib/decidim/debates/admin_engine.rb +++ b/decidim-debates/lib/decidim/debates/admin_engine.rb @@ -13,7 +13,6 @@ class AdminEngine < ::Rails::Engine routes do resources :debates do - post :archive, on: :member resources :debate_closes, only: [:edit, :update] end root to: "debates#index" diff --git a/decidim-debates/lib/decidim/debates/component.rb b/decidim-debates/lib/decidim/debates/component.rb index 6c396f4d83390..6a47a14db9a16 100644 --- a/decidim-debates/lib/decidim/debates/component.rb +++ b/decidim-debates/lib/decidim/debates/component.rb @@ -121,17 +121,12 @@ Decidim::Comments::Seed.comments_for(debate) end - Decidim::Debates::Debate.last(2).each do |debate| - debate.conclusions = Decidim::Faker::Localized.wrapped("

", "

") do - Decidim::Faker::Localized.paragraph(sentence_count: 3) - end - debate.closed_at = Time.current - debate.save! + closed_debate = Decidim::Debates::Debate.last + closed_debate.conclusions = Decidim::Faker::Localized.wrapped("

", "

") do + Decidim::Faker::Localized.paragraph(sentence_count: 3) end - - archived_debate = Decidim::Debates::Debate.last - archived_debate.archived_at = Time.current - archived_debate.save! + closed_debate.closed_at = Time.current + closed_debate.save! params = { component: component, diff --git a/decidim-debates/spec/commands/decidim/debates/admin/archive_debate_spec.rb b/decidim-debates/spec/commands/decidim/debates/admin/archive_debate_spec.rb deleted file mode 100644 index fc55ee80db4b4..0000000000000 --- a/decidim-debates/spec/commands/decidim/debates/admin/archive_debate_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe Decidim::Debates::Admin::ArchiveDebate do - subject { described_class.new(archive, debate, user) } - - let(:organization) { create :organization, available_locales: [:en, :ca, :es], default_locale: :en } - let(:participatory_process) { create :participatory_process, organization: organization } - let(:current_component) { create :component, participatory_space: participatory_process, manifest_name: "debates" } - let(:category) { create :category, participatory_space: participatory_process } - let(:user) { create :user, :admin, :confirmed, organization: organization } - let(:debate) { create :debate, component: current_component, archived_at: archived_at } - let(:archive) { true } - let(:archived_at) { nil } - - context "when the debate object is not valid" do - before do - debate.title = nil - end - - it "is not valid" do - expect { subject.call }.to broadcast(:invalid) - end - end - - context "when everything is ok" do - context "when the archive parameter is true" do - let(:archived_at) { nil } - let(:archive) { true } - - it "archives the debate" do - subject.call - expect(debate.archived_at).to be_present - end - end - - context "when the archive parameter is false" do - let(:archived_at) { 1.day.ago } - let(:archive) { false } - - it "unarchives the debate" do - subject.call - expect(debate.archived_at).not_to be_present - end - end - end -end diff --git a/decidim-debates/spec/models/decidim/debates/debate_spec.rb b/decidim-debates/spec/models/decidim/debates/debate_spec.rb index a0abe6411a1f6..ff5e5595e6a01 100644 --- a/decidim-debates/spec/models/decidim/debates/debate_spec.rb +++ b/decidim-debates/spec/models/decidim/debates/debate_spec.rb @@ -137,20 +137,4 @@ it { is_expected.to be_falsey } end end - - describe "archived?" do - subject { debate.archived? } - - context "when the debate has an archived_at time" do - let(:debate) { build :debate, archived_at: 2.days.ago } - - it { is_expected.to be_truthy } - end - - context "when the debate does not have an archived_at time" do - let(:debate) { build :debate, archived_at: nil } - - it { is_expected.to be_falsey } - end - end end diff --git a/decidim-debates/spec/permissions/decidim/debates/admin/permissions_spec.rb b/decidim-debates/spec/permissions/decidim/debates/admin/permissions_spec.rb index 37782ebb8bdc4..7571540a755d3 100644 --- a/decidim-debates/spec/permissions/decidim/debates/admin/permissions_spec.rb +++ b/decidim-debates/spec/permissions/decidim/debates/admin/permissions_spec.rb @@ -89,34 +89,4 @@ it { is_expected.to eq false } end end - - describe "debate archive" do - let(:action) do - { scope: :admin, action: :archive, subject: :debate } - end - - context "when the debate is closed" do - let(:debate) { create :debate, :closed, component: debates_component } - - it { is_expected.to eq true } - - context "and it is not official" do - let(:debate) { create :debate, :closed, author: user, component: debates_component } - - it { is_expected.to eq true } - end - end - - context "when debate is open" do - let(:debate) { create :debate, component: debates_component } - - it { is_expected.to eq true } - - context "and it is not official" do - let(:debate) { create :debate, author: user, component: debates_component } - - it { is_expected.to eq true } - end - end - end end diff --git a/decidim-debates/spec/shared/manage_debates_examples.rb b/decidim-debates/spec/shared/manage_debates_examples.rb index 4838b3e0ef57a..468999b7dea26 100644 --- a/decidim-debates/spec/shared/manage_debates_examples.rb +++ b/decidim-debates/spec/shared/manage_debates_examples.rb @@ -242,46 +242,4 @@ end end end - - describe "archiving a debate" do - let!(:debate) { create(:debate, :closed, component: current_component) } - - it "archives a debate" do - within find("tr", text: translated(debate.title)) do - page.find(".action-icon--archive").click - end - - within ".callout-wrapper" do - expect(page).to have_content("successfully") - end - - expect(page).to have_no_content(translated(debate.title)) - - click_link "Archived debates" - expect(page).to have_content(translated(debate.title)) - end - end - - describe "unarchiving a debate" do - let!(:debate) { create(:debate, :closed, component: current_component, archived_at: 1.day.ago) } - - before do - click_link "Archived debates" - end - - it "unarchives a debate" do - within find("tr", text: translated(debate.title)) do - page.find(".action-icon--archive").click - end - - within ".callout-wrapper" do - expect(page).to have_content("successfully") - end - - expect(page).to have_no_content(translated(debate.title)) - - click_link "Active debates" - expect(page).to have_content(translated(debate.title)) - end - end end diff --git a/decidim-dev/decidim-dev.gemspec b/decidim-dev/decidim-dev.gemspec index 719f964f30ac7..9cbbc6f7d4bfb 100644 --- a/decidim-dev/decidim-dev.gemspec +++ b/decidim-dev/decidim-dev.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |s| s.add_dependency "i18n-tasks", "~> 0.9.18" s.add_dependency "mdl", "~> 0.5" s.add_dependency "nokogiri", ">= 1.10.8" + s.add_dependency "puffing-billy", "~> 2.4.0" s.add_dependency "puma", ">= 4.3" s.add_dependency "rails-controller-testing", "~> 1.0" s.add_dependency "rspec-cells", "~> 0.3.4" diff --git a/decidim-dev/lib/decidim/dev/assets/public_key2.jwk b/decidim-dev/lib/decidim/dev/assets/public_key2.jwk new file mode 100644 index 0000000000000..6a4ca8e8c61b2 --- /dev/null +++ b/decidim-dev/lib/decidim/dev/assets/public_key2.jwk @@ -0,0 +1 @@ +{"alg":"RS256","e":"AQAB","kty":"RSA","n":"mYW4py55eHVzlAOLeWSlkX-UzFcWmPZNgt38YgfDsrWsMiF99NxeS1hHBBBAIMYVj4DdraunxwxibFZJm4B7JfLedO02pGHXWpx-VUSwRU7TKBO8KGcLIuyIsLAn4VZmcUCfOD4z4D3mOPXJFrmy_db4EQJgQnkRQdop9Nb-zExjYdw9IOnSFYdYLJ6k-g4Onu43kUIVDjBBwBlNHIkr-zpApPWhABUpSV_Vy6WAdku3ix4MyFQLQ48ljJp4KVKrw85km3x5k1p7yXpRXaeAUwdl1ucZUHgeb23ED-q2gwASAbrBLOMlrrRrexObRXlHd7gkQS4fwvLtehENY0EepaS6R9ZDGD5l8AJOlS3jGCBfQdpL9aJT05ZqmB8Vi0Fj8PgDkzselqp9eSwUBb82bp8padX9NsZPaokXLtJls8acSOPpBDwmEPAiUML7LWUwiRylimK4CVDNxulNGnBuPlYl5b4vZQHtpjBEFfKUZxI8qY1Y2qpEehaYD0oXa64yr2Hi5IVdlCOS88hqPskPttoe28cm6YVYflJP9bv4slqeu1cafDSCxX7A4PX83wDpW5gyWC3cXAPTXh2Plxlnn3p9KTae6_YPkI8Id26zkm61LpKi-CkQCvo2fHvfZI23JiSj3Y9KYZR25Ym8O9aaNBdhgvNKhB8VFlizKet3VU8"} \ No newline at end of file diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/capybara.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/capybara.rb index b9d2c6f2759d1..c5925c102fbb6 100644 --- a/decidim-dev/lib/decidim/dev/test/rspec_support/capybara.rb +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/capybara.rb @@ -77,6 +77,10 @@ def switch_to_secure_context_host end end + config.after(type: :system) do |example| + warn page.driver.browser.manage.logs.get(:browser) unless example.metadata[:driver].eql?(:rack_test) + end + config.include Decidim::CapybaraTestHelpers, type: :system config.include Devise::Test::IntegrationHelpers, type: :system end diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/component_context.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/component_context.rb index f63af8edfc7c4..df35e1468c96a 100644 --- a/decidim-dev/lib/decidim/dev/test/rspec_support/component_context.rb +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/component_context.rb @@ -2,10 +2,9 @@ shared_context "with a component" do let(:manifest) { Decidim.find_component_manifest(manifest_name) } - let(:user) { create :user, :confirmed, organization: organization } - let!(:organization) { create(:organization, available_authorizations: %w(dummy_authorization_handler another_dummy_authorization_handler)) } + let!(:organization) { create(:organization, *organization_traits, available_authorizations: %w(dummy_authorization_handler another_dummy_authorization_handler)) } let(:participatory_process) do create(:participatory_process, :with_steps, organization: organization) @@ -22,8 +21,14 @@ let!(:category) { create :category, participatory_space: participatory_space } let!(:scope) { create :scope, organization: organization } + let(:organization_traits) { [] } + before do - switch_to_host(organization.host) + if organization_traits.include?(:secure_context) + switch_to_secure_context_host + else + switch_to_host(organization.host) + end end def visit_component @@ -32,9 +37,12 @@ def visit_component end shared_context "when managing a component" do - include_context "with a component" + include_context "with a component" do + let(:organization_traits) { component_organization_traits } + end let(:current_component) { component } + let(:component_organization_traits) { [] } before do login_as user, scope: :user @@ -56,7 +64,11 @@ def edit_component_path(component) end shared_context "when managing a component as an admin" do - include_context "when managing a component" + include_context "when managing a component" do + let(:component_organization_traits) { admin_component_organization_traits } + end + + let(:admin_component_organization_traits) { [] } let(:user) do create :user, diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/puffing_billy.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/puffing_billy.rb new file mode 100644 index 0000000000000..225ab479aaf17 --- /dev/null +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/puffing_billy.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "billy/capybara/rspec" + +Billy.configure do |config| + config.cache = true + config.persist_cache = true + config.cache_path = "spec/billy" + config.record_requests = true + config.proxied_request_connect_timeout = 20 + config.proxied_request_inactivity_timeout = 20 +end + +RSpec.configure do |config| + base_cache_path = Billy.config.cache_path + + config.before :each, :billy do |example| + driven_by :selenium_chrome_headless_billy + switch_to_secure_context_host + WebMock::HttpLibAdapters::EmHttpRequestAdapter.disable! + + feature_name = example.metadata[:example_group][:description].underscore.gsub(" ", "_") + scenario_name = example.metadata[:description].underscore.gsub(" ", "_") + cache_scenario_folder_path = File.join(base_cache_path, feature_name, scenario_name) + FileUtils.mkdir_p(cache_scenario_folder_path) + Billy.config.cache_path = cache_scenario_folder_path + end +end diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/vcr.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/vcr.rb index 7215969e8b072..58bf5a9049530 100644 --- a/decidim-dev/lib/decidim/dev/test/rspec_support/vcr.rb +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/vcr.rb @@ -2,16 +2,26 @@ require "vcr" +module BulletinBoardVcr + def self.bulletin_board_uri?(uri) + uri.hostname == bulletin_board_uri.hostname && uri.port == bulletin_board_uri.port + end + + def self.bulletin_board_uri + @bulletin_board_uri ||= URI(bulletin_board_server) + end + + def self.bulletin_board_server + return "" unless defined?(Decidim::Elections) + + Decidim::Elections.bulletin_board.server + end +end + VCR.configure do |config| config.default_cassette_options = { serialize_with: :json } config.cassette_library_dir = "spec/cassettes" config.hook_into :webmock config.configure_rspec_metadata! - config.ignore_request do |request| - if defined?(Decidim::Elections) - URI(request.uri).port != URI(Decidim::Elections.bulletin_board.server).port - else - true - end - end + config.ignore_request { |request| !BulletinBoardVcr.bulletin_board_uri?(URI(request.uri)) } end diff --git a/decidim-dev/lib/decidim/dev/test/rspec_support/download_helper.rb b/decidim-dev/lib/decidim/dev/test/rspec_support/z_download_helper.rb similarity index 59% rename from decidim-dev/lib/decidim/dev/test/rspec_support/download_helper.rb rename to decidim-dev/lib/decidim/dev/test/rspec_support/z_download_helper.rb index a374a5e7218f7..85d83ed157388 100644 --- a/decidim-dev/lib/decidim/dev/test/rspec_support/download_helper.rb +++ b/decidim-dev/lib/decidim/dev/test/rspec_support/z_download_helper.rb @@ -4,28 +4,28 @@ module DownloadHelper TIMEOUT = 10 PATH = Rails.root.join("tmp/downloads").freeze - def downloads - Dir[PATH.join("*")] + def downloads(name = nil) + Dir[PATH.join(name || "*")] end - def download_path - wait_for_download - downloads.first + def download_path(name = nil) + wait_for_download(name) + downloads(name).first end - def download_content - wait_for_download - File.read(download_path) + def download_content(name = nil) + wait_for_download(name) + File.read(download_path(name)) end - def wait_for_download + def wait_for_download(name = nil) Timeout.timeout(TIMEOUT) do - sleep 0.1 until downloaded? + sleep 0.1 until downloaded?(name) end end - def downloaded? - downloads.any? && !downloading? + def downloaded?(name = nil) + downloads(name).any? && !downloading? end def downloading? @@ -40,8 +40,6 @@ def clear_downloads RSpec.configure do |config| config.include DownloadHelper, download: true config.before :each, download: true do - driven_by(:headless_chrome) - switch_to_default_host FileUtils.mkdir_p DownloadHelper::PATH.to_s page.driver.browser.download_path = DownloadHelper::PATH.to_s clear_downloads diff --git a/decidim-elections/app/assets/config/decidim_elections_manifest.js b/decidim-elections/app/assets/config/decidim_elections_manifest.js index e177f07cca992..d7e36e4862cbd 100644 --- a/decidim-elections/app/assets/config/decidim_elections_manifest.js +++ b/decidim-elections/app/assets/config/decidim_elections_manifest.js @@ -1,3 +1,4 @@ +//= link decidim/elections/trustee_zone/key_ceremony.js //= link decidim/elections/trustee_zone.js //= link decidim/elections/vote.js //= link_tree ../images/decidim diff --git a/decidim-elections/app/assets/javascripts/decidim/elections/identification_keys.js.es6 b/decidim-elections/app/assets/javascripts/decidim/elections/identification_keys.js.es6 index d08c92084557a..985135bf08112 100644 --- a/decidim-elections/app/assets/javascripts/decidim/elections/identification_keys.js.es6 +++ b/decidim-elections/app/assets/javascripts/decidim/elections/identification_keys.js.es6 @@ -1,10 +1,11 @@ +/* eslint-disable prefer-reflect */ /** * IdentificationKeys component. */ ((exports) => { class IdentificationKeys { - constructor(trusteeId, storedPublicKey) { + constructor(trusteeUniqueId, storedPublicKey) { this.format = "jwk"; this.algorithm = { name: "RSASSA-PKCS1-v1_5", @@ -16,11 +17,11 @@ this.publicKeyAttrs = ["alg", "e", "kty", "n"]; this.jwtHeader = this._encode64(JSON.stringify({alg: "RS256", typ: "JWT"})); - this.trusteeId = trusteeId; + this.trusteeUniqueId = trusteeUniqueId; this.privateKey = null; this.publicKey = null; this.storedPublicKey = JSON.parse(storedPublicKey || null); - this.keyIdentifier = `${trusteeId}_identification_key`; + this.keyIdentifier = `${trusteeUniqueId}-private-key`; this.browserSupport = this._checkBrowserSupport(); this.textEncoder = new TextEncoder("utf-8"); @@ -113,15 +114,14 @@ return this._clear(); } - sign(payload) { + async sign(payload) { if (!this.browserSupport || this.privateKey === null) { return false; } const data = `${this.jwtHeader}.${this._encode64(JSON.stringify(payload))}`; - const signature = this.crypto.subtle.sign(this.algorithm.name, this.privateKey, this.textEncoder.encode(data)); - - return `${data}.${this._encode64(signature)}`; + const signature = await this.crypto.subtle.sign(this.algorithm, this.privateKey, this.textEncoder.encode(data)); + return `${data}.${btoa(Reflect.apply(String.fromCharCode, null, new Uint8Array(signature))).replace(/[=]/g, "").replace(/\+/g, "-").replace(/\//g, "_")}`; } _checkBrowserSupport() { diff --git a/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone.js.es6 b/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone.js.es6 index b49719be8b294..a9302ba29b215 100644 --- a/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone.js.es6 +++ b/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone.js.es6 @@ -7,10 +7,10 @@ window.Decidim = window.Decidim || {}; $(() => { function identificationKeys() { const $form = $(".trustee_zone form"); - const $trusteeId = $("#trustee_id", $form); + const $trusteeUniqueId = $("#trustee_unique_id", $form); const $trusteePublicKey = $("#trustee_public_key", $form); - window.trusteeIdentificationKeys = new window.Decidim.IdentificationKeys(`trustee-${$trusteeId.val()}`, $trusteePublicKey.val()); + window.trusteeIdentificationKeys = new window.Decidim.IdentificationKeys($trusteeUniqueId.val(), $trusteePublicKey.val()); if (!window.trusteeIdentificationKeys.browserSupport) { $("#not_supported_browser").addClass("visible"); return; diff --git a/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone/key_ceremony.js.es6 b/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone/key_ceremony.js.es6 new file mode 100644 index 0000000000000..fcc617ee4a6f2 --- /dev/null +++ b/decidim-elections/app/assets/javascripts/decidim/elections/trustee_zone/key_ceremony.js.es6 @@ -0,0 +1,174 @@ +/* eslint-disable no-alert */ + +// = require decidim/bulletin_board/decidim-bulletin_board +// = require ../identification_keys + +/** + * This file is responsible to generate election keys, + * create a backup of keys for the trustee and + * update the election bulletin board status + */ + +$(() => { + const $keyCeremony = $(".key-ceremony"); + const $startButton = $keyCeremony.find(".start"); + const $backButton = $keyCeremony.find(".back"); + const $backupModal = $("#show-backup-modal"); + const $backupButton = $backupModal.find(".download-election-keys"); + const $restoreModal = $("#show-restore-modal"); + const $restoreButton = $restoreModal.find(".upload-election-keys"); + + const electionKeyIdentifier = `${$keyCeremony.data( + "trusteeUniqueId" + )}-election-${$keyCeremony.data("electionId")}`; + + let wrapperState = ""; + let currentStep = null; + + const trusteeContext = { + uniqueId: $keyCeremony.data("trusteeUniqueId"), + publicKeyJSON: JSON.stringify($keyCeremony.data("trusteePublicKey")) + }; + const identificationKeys = new window.Decidim.IdentificationKeys( + trusteeContext.uniqueId, + trusteeContext.publicKeyJSON + ); + + // updates the BulletinBoard Election Status + const updateElectionStatus = () => { + $.ajax({ + method: "PUT", + url: $keyCeremony.data("updateElectionStatusUrl"), + contentType: "application/json", + headers: { + "X-CSRF-Token": $("meta[name=csrf-token]").attr("content") + } + }); + }; + + const getStepRow = (step) => { + return $(`#${step.replace(".", "-")}`); + }; + + const completeProcess = () => { + const $allSteps = $(".step_status"); + $allSteps.attr("data-step-status", "completed"); + + $startButton.addClass("hide"); + $backButton.removeClass("hide"); + updateElectionStatus(); + }; + + // generates the keys + identificationKeys.present(async (exists) => { + if (exists) { + const { + Client, + KeyCeremony, + MessageIdentifier + } = window.decidimBulletinBoard; + + const bulletinBoardClient = new Client({ + apiEndpointUrl: $keyCeremony.data("apiEndpointUrl"), + wsEndpointUrl: $keyCeremony.data("websocketUrl") + }); + + const keyCeremony = new KeyCeremony({ + bulletinBoardClient, + electionContext: { + id: `${$keyCeremony.data("authorityUniqueId")}.${$keyCeremony.data( + "electionId" + )}`, + currentTrusteeContext: { + id: trusteeContext.uniqueId, + identificationKeys + } + } + }); + + $startButton.on("click", async () => { + $startButton.prop("disabled", true); + + if (keyCeremony.restoreNeeded()) { + $restoreModal.foundation("open"); + } else { + keyCeremony.run(); + } + }); + + $backupButton.on("click", () => { + let element = document.createElement("a"); + element.setAttribute( + "href", + `data:text/plain;charset=utf-8,${wrapperState}` + ); + element.setAttribute("download", `${electionKeyIdentifier}.bak`); + element.style.display = "none"; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + $backupModal.foundation("close"); + keyCeremony.run(); + }); + + $restoreButton.on("click", () => { + let element = document.createElement("input"); + element.setAttribute("type", "file"); + element.setAttribute("accept", ".bak"); + element.style.display = "none"; + document.body.appendChild(element); + + element.addEventListener("change", (event) => { + document.body.removeChild(element); + + let file = event.target.files[0]; + const reader = new FileReader(); + reader.onload = function(evt) { + let content = evt.target.result; + if (keyCeremony.restore(content)) { + $startButton.removeClass("disabled"); + $restoreModal.foundation("close"); + keyCeremony.run(); + } else { + element.click(); + } + }; + reader.readAsText(file); + }); + element.click(); + }); + + keyCeremony.events.subscribe((event) => { + let messageIdentifier = MessageIdentifier.parse( + event.message.messageId + ); + if (event.type === "[Message] Received") { + if (currentStep && currentStep !== messageIdentifier.typeSubtype) { + const $previousStep = getStepRow(currentStep); + $previousStep.attr("data-step-status", "completed"); + } + currentStep = messageIdentifier.typeSubtype; + + const $currentStep = getStepRow(currentStep); + if ($currentStep.data("step-status") !== "completed") { + $currentStep.attr("data-step-status", "processing"); + } + } + + if (event.type === "[Message] Processed" && event.result) { + if (event.result.save) { + wrapperState = keyCeremony.backup(); + $backupModal.foundation("open"); + } + + if (event.result.done) { + completeProcess(); + } + } + }); + + await keyCeremony.setup(); + $startButton.prop("disabled", false); + } + }); +}); diff --git a/decidim-elections/app/assets/javascripts/decidim/elections/vote.js.es6 b/decidim-elections/app/assets/javascripts/decidim/elections/vote.js.es6 index dc4d4e5ef3b40..ea9e740320bac 100644 --- a/decidim-elections/app/assets/javascripts/decidim/elections/vote.js.es6 +++ b/decidim-elections/app/assets/javascripts/decidim/elections/vote.js.es6 @@ -1,15 +1,17 @@ /* eslint-disable require-jsdoc, prefer-template, func-style, id-length, no-use-before-define, init-declarations, no-invalid-this */ /* eslint no-unused-vars: ["error", { "args": "none" }] */ +// = require decidim/bulletin_board/decidim-bulletin_board $(() => { - const $vote = $(".focus"); - const $continueButton = $vote.find("a.focus__next"); - const $confirmButton = $vote.find("a.focus__next.confirm"); - const $continueSpan = $vote.find("span.disabled-continue"); + const { Voter, Client } = decidimBulletinBoard; + const $voteWrapper = $(".vote-wrapper"); + const $continueButton = $voteWrapper.find("a.focus__next"); + const $confirmButton = $voteWrapper.find("a.focus__next.confirm"); + const $continueSpan = $voteWrapper.find("span.disabled-continue"); let $answerCounter = 0; let $currentStep, $currentStepMaxSelection = ""; - let $formData = $vote.find(".answer_input"); + let $formData = $voteWrapper.find(".answer_input"); function initStep() { setCurrentStep(); @@ -23,7 +25,7 @@ $(() => { initStep() function setCurrentStep() { - $currentStep = $vote.find(".focus__step:visible") + $currentStep = $voteWrapper.find(".focus__step:visible") setSelections(); onSelectionChange(); } @@ -120,13 +122,13 @@ $(() => { // get form Data function getFormData(formData) { - let unindexedArray = formData.serializeArray(); - let indexedArray = {}; - $.map(unindexedArray, function(n, i) { - indexedArray[n.name] = n.value; - }); - - return indexedArray; + return formData.serializeArray().reduce((acc, {name, value}) => { + if (!acc[name]) { + acc[name] = []; + } + acc[name] = [...acc[name], value]; + return acc; + }, {"ballot_style": "unique"}); } // confirm vote @@ -137,15 +139,61 @@ $(() => { }); // cast vote - function castVote(boothMode, formData) { - // log form Data - console.log(`Your vote got encrypted successfully. The booth mode is ${boothMode}. Your vote content is:`, formData) // eslint-disable-line no-console + function castVote(_boothMode, formData) { + const bulletinBoardClient = new Client({ + apiEndpointUrl: $voteWrapper.data("apiEndpointUrl"), + wsEndpointUrl: $voteWrapper.data("websocketUrl") + }); + + const voter = new Voter({ + id: $voteWrapper.data("voterId"), + bulletinBoardClient, + electionContext: { + id: $voteWrapper.data("electionUniqueId") + } + }); - window.setTimeout(function() { - $($vote).find("#encrypting").addClass("hide") - $($vote).find("#confirmed_page").removeClass("hide") + let encryptedVoteHashToVerify = null; + + voter.encrypt(formData).then((encryptedVoteAsJSON) => { + return crypto.subtle.digest("SHA-256", new TextEncoder().encode(encryptedVoteAsJSON)).then((hashBuffer) => { + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); + + return { + encryptedVote: encryptedVoteAsJSON, + encryptedVoteHash: hashHex + }; + }) + }).then(({ encryptedVote, encryptedVoteHash}) => { + encryptedVoteHashToVerify = encryptedVoteHash; + + return $.ajax({ + method: "POST", + url: $voteWrapper.data("castVoteUrl"), + contentType: "application/json", + data: JSON.stringify({ encrypted_vote: encryptedVote, encrypted_vote_hash: encryptedVoteHash }), // eslint-disable-line camelcase + headers: { + "X-CSRF-Token": $("meta[name=csrf-token]").attr("content") + } + }); + }).then(() => { + $voteWrapper.find("#encrypting").addClass("hide"); + $voteWrapper.find("#confirmed_page").removeClass("hide"); + $voteWrapper.find(".vote-confirmed-result").hide(); window.confirmed = true; - }, 3000) + + if ($voteWrapper.data("booth-mode") === "preview") { + return new Promise((resolve) => { + setTimeout(resolve, 500); + }); + } + + return voter.verifyVote(encryptedVoteHashToVerify); + }).then(() => { + $voteWrapper.find(".vote-confirmed-processing").hide(); + $voteWrapper.find(".vote-confirmed-result").show(); + }) } // exit message before confirming diff --git a/decidim-elections/app/assets/stylesheets/decidim/elections/focus/_evote.scss b/decidim-elections/app/assets/stylesheets/decidim/elections/focus/_evote.scss index 38a5bce7bd02f..8919fa8d5fc82 100644 --- a/decidim-elections/app/assets/stylesheets/decidim/elections/focus/_evote.scss +++ b/decidim-elections/app/assets/stylesheets/decidim/elections/focus/_evote.scss @@ -1,3 +1,8 @@ +.vote-wrapper{ + display: flex; + flex-grow: 1; +} + .evote{ $checkfocus: 1px dotted rgba(black, .5); $checkfocus-offset: .1em; diff --git a/decidim-elections/app/assets/stylesheets/decidim/elections/trustee_zone.scss b/decidim-elections/app/assets/stylesheets/decidim/elections/trustee_zone.scss index 04336bade4fb5..7424e121321b0 100644 --- a/decidim-elections/app/assets/stylesheets/decidim/elections/trustee_zone.scss +++ b/decidim-elections/app/assets/stylesheets/decidim/elections/trustee_zone.scss @@ -10,3 +10,15 @@ } } } + +.step_status{ + span{ + display: none; + } + + &[data-step-status="pending"] .pending, + &[data-step-status="processing"] .processing, + &[data-step-status="completed"] .completed{ + display: block; + } +} diff --git a/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-header.scss b/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-header.scss new file mode 100644 index 0000000000000..0b73b29c39ecb --- /dev/null +++ b/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-header.scss @@ -0,0 +1,99 @@ +.votings-header{ + margin-bottom: $margin-sm; + + @include breakpoint(medium){ + margin-bottom: $margin-m; + } + + .heading2{ + font-weight: bold; + } +} + +.votings-header__main{ + background-size: cover; + position: relative; + z-index: 0; + display: flex; + flex-direction: column; + + &::after{ + content: ""; + display: block; + position: absolute; + z-index: -1; + top: 0; + width: 100%; + height: 100%; + background-color: rgba($black, .6); + } +} + +.votings-header__container{ + position: relative; + z-index: 1; + padding: 0 0 0 1rem; + + @include breakpoint(mediumlarge){ + display: flex; + align-items: stretch; + + .columns{ + > *{ + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + } + } + } +} + +.votings-header__info{ + padding: 1rem 1.5rem 1rem 1rem; + text-align: center; + + @include breakpoint(medium){ + padding: 1rem 1.5rem 1rem 1rem; + } + + @include breakpoint(mediumlarge){ + padding-left: 2rem; + text-align: left; + } +} + +.votings-header__extra{ + background: rgba($dark-gray, .8); + color: $white; + text-align: center; + margin-left: auto; + margin-right: auto; + max-width: 16rem; + padding: 1rem; + margin-bottom: 1.5rem; + + @include breakpoint(medium){ + margin-left: -1rem; + margin-right: 0; + margin-bottom: 0; + max-width: 150%; + padding: 2rem 1rem; + } +} + +.votings-header__extra-title{ + display: block; + font-weight: 600; + font-size: 1rem; + + @include breakpoint(medium){ + font-size: 1.1rem; + } + + line-height: 1; +} + +.votings-header__extra-date{ + font-weight: 900; +} diff --git a/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-home-banner.scss b/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-home-banner.scss new file mode 100644 index 0000000000000..531dd52c6ed30 --- /dev/null +++ b/decidim-elections/app/assets/stylesheets/decidim/votings/votings/_votings-home-banner.scss @@ -0,0 +1,11 @@ +.votings-home-banner{ + .votings-header__main{ + &::after{ + display: none; + } + } + + .votings-header__container{ + min-height: 8rem; + } +} diff --git a/decidim-elections/app/cells/decidim/votings/voting_cell.rb b/decidim-elections/app/cells/decidim/votings/voting_cell.rb new file mode 100644 index 0000000000000..7bedd479c3234 --- /dev/null +++ b/decidim-elections/app/cells/decidim/votings/voting_cell.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # This cell renders the card for an instance of a Voting + # the default size is the Medium Card (:m) + class VotingCell < Decidim::ViewModel + def show + cell card_size, model, options + end + + private + + def card_size + "decidim/votings/voting_m" + end + end + end +end diff --git a/decidim-elections/app/cells/decidim/votings/voting_m/data.erb b/decidim-elections/app/cells/decidim/votings/voting_m/data.erb new file mode 100644 index 0000000000000..2dca8481da3a5 --- /dev/null +++ b/decidim-elections/app/cells/decidim/votings/voting_m/data.erb @@ -0,0 +1,21 @@ +
+ +
diff --git a/decidim-elections/app/cells/decidim/votings/voting_m/footer.erb b/decidim-elections/app/cells/decidim/votings/voting_m/footer.erb new file mode 100644 index 0000000000000..326559664edf2 --- /dev/null +++ b/decidim-elections/app/cells/decidim/votings/voting_m/footer.erb @@ -0,0 +1,5 @@ + diff --git a/decidim-elections/app/cells/decidim/votings/voting_m_cell.rb b/decidim-elections/app/cells/decidim/votings/voting_m_cell.rb new file mode 100644 index 0000000000000..f9c59b3896bb9 --- /dev/null +++ b/decidim-elections/app/cells/decidim/votings/voting_m_cell.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # This cell renders the Medium (:m) voting card + # for an given instance of a Voting + class VotingMCell < Decidim::CardMCell + private + + def has_state? + false + end + + # Even though we need to render the badge, we can't do it in the normal + # way, because the paragraph comes from a user input and contains HTML. + # This causes the badge and the paragraph to appear in different lines. + # In order to fix it, check the `description` method. + def has_badge? + false + end + + def badge_name + text = model.period_status + return unless text + + I18n.t(text, scope: "decidim.votings.votings_m.badge_name") + end + + # In order to render the badge inline with the paragraph text we need to + # find the opening `

` tag and include the badge right after it. This + # makes the layout look good. + def description + text = super + text.gsub!(/^

/, "

#{render :badge}") + html_truncate(text, length: 100) + end + + def resource_path + Decidim::Votings::Engine.routes.url_helpers.voting_path(model) + end + + def resource_image_path + model.banner_image.url + end + + def has_image? + model.banner_image.url.present? + end + + def start_time + model.start_time + end + + def end_time + model.end_time + end + + def footer_button_text + "view" + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/elections/admin/add_user_as_trustee.rb b/decidim-elections/app/commands/decidim/elections/admin/add_user_as_trustee.rb index 52f9b9a3c314c..d62edb7d219e0 100644 --- a/decidim-elections/app/commands/decidim/elections/admin/add_user_as_trustee.rb +++ b/decidim-elections/app/commands/decidim/elections/admin/add_user_as_trustee.rb @@ -48,7 +48,7 @@ def existing_trustee_participatory_spaces? @existing_trustee_participatory_spaces ||= Decidim::Elections::Trustee.joins(:trustees_participatory_spaces) .includes([:user]) .where(trustees_participatory_spaces: trustees_space) - .where("decidim_user_id = ?", form.user.id).any? + .where(decidim_user_id: form.user.id).any? end # if there's no user - trustee relation, the trustee gets created and the notification diff --git a/decidim-elections/app/commands/decidim/elections/admin/close_ballot_box.rb b/decidim-elections/app/commands/decidim/elections/admin/close_ballot_box.rb new file mode 100644 index 0000000000000..bfcce75d65ac3 --- /dev/null +++ b/decidim-elections/app/commands/decidim/elections/admin/close_ballot_box.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Admin + # This command gets called to close the ballot box in the Bulletin Board. + class CloseBallotBox < Rectify::Command + # Public: Initializes the command. + # + # form - A BallotBoxForm object with the information needed to open or close the ballot box + def initialize(form) + @form = form + end + + # Public: Close the ballot box for the Election. + # + # Broadcasts :ok if setup, :invalid otherwise. + def call + return broadcast(:invalid) if form.invalid? + + transaction do + log_action + close_ballot_box + end + + broadcast(:ok) + rescue StandardError => e + broadcast(:invalid, e.message) + end + + private + + attr_accessor :form + + delegate :election, :bulletin_board, to: :form + + def log_action + Decidim.traceability.perform_action!( + :close_ballot_box, + election, + form.current_user, + visibility: "all" + ) + end + + def close_ballot_box + bb_election = bulletin_board.close_ballot_box(election.id) + store_bulletin_board_status(bb_election.status) + end + + def store_bulletin_board_status(bb_status) + election.bb_status = bb_status + election.save + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/elections/admin/open_ballot_box.rb b/decidim-elections/app/commands/decidim/elections/admin/open_ballot_box.rb new file mode 100644 index 0000000000000..5fe9aa2e1d49c --- /dev/null +++ b/decidim-elections/app/commands/decidim/elections/admin/open_ballot_box.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Admin + # This command gets called to open the ballot box in the Bulletin Board. + class OpenBallotBox < Rectify::Command + # Public: Initializes the command. + # + # form - A BallotBoxForm object with the information needed to open or close the ballot box + def initialize(form) + @form = form + end + + # Public: Open the ballot box for the Election. + # + # Broadcasts :ok if setup, :invalid otherwise. + def call + return broadcast(:invalid) if form.invalid? + + transaction do + log_action + open_ballot_box + end + + broadcast(:ok) + rescue StandardError => e + broadcast(:invalid, e.message) + end + + private + + attr_accessor :form + + delegate :election, :bulletin_board, to: :form + + def log_action + Decidim.traceability.perform_action!( + :open_ballot_box, + election, + form.current_user, + visibility: "all" + ) + end + + def open_ballot_box + bb_election = bulletin_board.open_ballot_box(election.id) + store_bulletin_board_status(bb_election.status) + rescue StandardError => e + broadcast(:invalid, e.message) + raise ActiveRecord::Rollback + end + + def store_bulletin_board_status(bb_status) + election.bb_status = bb_status + election.save + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/elections/admin/setup_election.rb b/decidim-elections/app/commands/decidim/elections/admin/setup_election.rb index 442cf4dc9d502..a37bc7ea9bae7 100644 --- a/decidim-elections/app/commands/decidim/elections/admin/setup_election.rb +++ b/decidim-elections/app/commands/decidim/elections/admin/setup_election.rb @@ -7,12 +7,9 @@ module Admin class SetupElection < Rectify::Command # Public: Initializes the command. # - # election - The election to setup. - # bulletin_board - The BulletinBoard Client that includes the GraphLient Client - def initialize(form, bulletin_board: Decidim::Elections.bulletin_board) - @election = form.election + # form - A SetupForm object with the information needed to setup the election + def initialize(form) @form = form - @bulletin_board = bulletin_board end # Public: Setup the Election. @@ -23,21 +20,21 @@ def call transaction do add_trustees_to_election - setup_election log_action notify_trustee_about_election + setup_election end - if form.errors.any? - broadcast(:invalid) - else - broadcast(:ok, election) - end + broadcast(:ok, election) + rescue StandardError => e + broadcast(:invalid, e.message) end private - attr_reader :election, :form, :bulletin_board + attr_reader :form + + delegate :election, :bulletin_board, to: :form def questions @questions ||= election.questions @@ -58,21 +55,18 @@ def add_trustees_to_election election.save! end - def election_id - "#{bulletin_board.authority_slug}.#{election.id}" - end - def election_data { iat: Time.now.to_i, - election_id: election_id, - message_id: "#{election_id}.create_election+a.#{bulletin_board.authority_slug}", - type: "create_election", scheme: bulletin_board.scheme, + authority: { + name: bulletin_board.authority_name, + public_key: bulletin_board.public_key + }, trustees: trustees.collect do |trustee| { - name: trustee.user.name, + name: trustee.name, public_key: trustee.public_key } end, @@ -134,15 +128,8 @@ def election_data end def setup_election - response = bulletin_board.setup_election(election_data) - - if response.error.present? - error = response.error - form.errors.add(:base, error) - raise ActiveRecord::Rollback - else - store_bulletin_board_status(response.election.status) - end + bb_election = bulletin_board.create_election(election.id, election_data) + store_bulletin_board_status(bb_election.status) end def log_action @@ -167,10 +154,9 @@ def notify_trustee_about_election end def store_bulletin_board_status(bb_status) - @election.update!( - blocked_at: Time.current, - bb_status: bb_status - ) + election.blocked_at = Time.current + election.bb_status = bb_status + election.save end end end diff --git a/decidim-elections/app/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status.rb b/decidim-elections/app/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status.rb new file mode 100644 index 0000000000000..56fb0914f5535 --- /dev/null +++ b/decidim-elections/app/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module TrusteeZone + # This command updates the election status if it got changed + class UpdateElectionBulletinBoardStatus < Rectify::Command + # Public: Initializes the command. + # + # status - The actual election status + def initialize(election, required_status) + @election = election + @required_status = required_status + end + + # Update the election if status got changed. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + return broadcast(:ok, election) unless election.bb_status.to_sym == required_status + + update_election_status! + + broadcast(:ok, election) + end + + private + + attr_reader :election, :required_status + + def update_election_status! + status = Decidim::Elections.bulletin_board.get_status(election.id) + election.bb_status = status + election.save! + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/elections/trustee_zone/update_trustee.rb b/decidim-elections/app/commands/decidim/elections/trustee_zone/update_trustee.rb index f51084ebfdf8b..a3a0933ff6a86 100644 --- a/decidim-elections/app/commands/decidim/elections/trustee_zone/update_trustee.rb +++ b/decidim-elections/app/commands/decidim/elections/trustee_zone/update_trustee.rb @@ -31,6 +31,7 @@ def call def update_trustee! trustee.update!( + name: form.name, public_key: form.public_key ) end diff --git a/decidim-elections/app/commands/decidim/elections/voter/cast_vote.rb b/decidim-elections/app/commands/decidim/elections/voter/cast_vote.rb new file mode 100644 index 0000000000000..0970f75a0c290 --- /dev/null +++ b/decidim-elections/app/commands/decidim/elections/voter/cast_vote.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Voter + # This command allows the user to store and cast their vote. + class CastVote < Rectify::Command + # Public: Initializes the command. + # + # form - A form with necessary info to cast a vote. + def initialize(form) + @form = form + end + + # Store and cast the vote + # + # Broadcasts :ok if successful, :invalid otherwise + def call + return broadcast(:invalid) if form.invalid? + + transaction do + store_vote + cast_vote_on_bulletin_board + end + + broadcast(:ok) + rescue StandardError => e + broadcast(:invalid, e.message) + end + + private + + attr_reader :form + + delegate :bulletin_board, to: :form + + def cast_vote_on_bulletin_board + bulletin_board.cast_vote(form.election_id, form.voter_id, form.encrypted_vote) + end + + def store_vote + Vote.create!( + election: form.election, + voter_id: form.voter_id, + encrypted_vote_hash: form.encrypted_vote_hash, + status: Vote::PENDING_STATUS + ) + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/votings/admin/create_voting.rb b/decidim-elections/app/commands/decidim/votings/admin/create_voting.rb index 1bff43b7f276f..1d009a7e7ec86 100644 --- a/decidim-elections/app/commands/decidim/votings/admin/create_voting.rb +++ b/decidim-elections/app/commands/decidim/votings/admin/create_voting.rb @@ -46,7 +46,9 @@ def create_voting! description: form.description, scope: form.scope, start_time: form.start_time, - end_time: form.end_time + end_time: form.end_time, + banner_image: form.banner_image, + introductory_image: form.introductory_image ) end end diff --git a/decidim-elections/app/commands/decidim/votings/admin/publish_voting.rb b/decidim-elections/app/commands/decidim/votings/admin/publish_voting.rb new file mode 100644 index 0000000000000..8045c7b03a0b6 --- /dev/null +++ b/decidim-elections/app/commands/decidim/votings/admin/publish_voting.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Decidim + module Votings + module Admin + # This command gets called when a voting is published from the admin panel. + class PublishVoting < Rectify::Command + # Public: Initializes the command. + # + # voting - The voting to publish. + # current_user - the user performing the action + def initialize(voting, current_user) + @voting = voting + @current_user = current_user + end + + # Public: Publishes the Voting. + # + # Broadcasts :ok if published, :invalid otherwise. + def call + return broadcast(:invalid) if voting.nil? || voting.published? + + publish_voting + + broadcast(:ok, voting) + end + + private + + attr_reader :voting, :current_user + + def publish_voting + Decidim.traceability.perform_action!(:publish, voting, current_user, visibility: "all") do + voting.publish! + end + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/votings/admin/unpublish_voting.rb b/decidim-elections/app/commands/decidim/votings/admin/unpublish_voting.rb new file mode 100644 index 0000000000000..0aeb4b5828806 --- /dev/null +++ b/decidim-elections/app/commands/decidim/votings/admin/unpublish_voting.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Decidim + module Votings + module Admin + # This command gets called when a voting is unpublished from the admin panel. + class UnpublishVoting < Rectify::Command + # Public: Initializes the command. + # + # voting - The voting to unpublish. + # current_user - the user performing the action + def initialize(voting, current_user) + @voting = voting + @current_user = current_user + end + + # Public: Unpublishes the Voting. + # + # Broadcasts :ok if unpublished, :invalid otherwise. + def call + return broadcast(:invalid) if voting.nil? || !voting.published? + + unpublish_voting + + broadcast(:ok, voting) + end + + private + + attr_reader :voting, :current_user + + def unpublish_voting + Decidim.traceability.perform_action!(:unpublish, voting, current_user, visibility: "all") do + voting.unpublish! + end + end + end + end + end +end diff --git a/decidim-elections/app/commands/decidim/votings/admin/update_voting.rb b/decidim-elections/app/commands/decidim/votings/admin/update_voting.rb new file mode 100644 index 0000000000000..4fc881c071123 --- /dev/null +++ b/decidim-elections/app/commands/decidim/votings/admin/update_voting.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Decidim + module Votings + module Admin + # A command with all the business logic when updating an existing + # voting in the system. + class UpdateVoting < Rectify::Command + # Public: Initializes the command. + # + # voting - the Voting to update + # form - A form object with the params. + def initialize(voting, form) + @voting = voting + @form = form + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid. + # - :invalid if the form wasn't valid and we couldn't proceed. + # + # Returns nothing. + def call + return broadcast(:invalid) if form.invalid? + + update_voting! + + if voting.valid? + broadcast(:ok, voting) + else + form.errors.add(:banner_image, voting.errors[:banner_image]) if voting.errors.include? :banner_image + form.errors.add(:introductory_image, voting.errors[:introductory_image]) if voting.errors.include? :introductory_image + broadcast(:invalid) + end + end + + private + + attr_reader :form, :voting + + def update_voting! + voting.assign_attributes(attributes) + return unless voting.valid? + + voting.save! + + Decidim.traceability.perform_action!(:update, voting, form.current_user) do + voting + end + end + + def attributes + { + title: form.title, + description: form.description, + slug: form.slug, + start_time: form.start_time, + end_time: form.end_time, + scope: form.scope + }.merge(uploader_attributes) + end + + def uploader_attributes + { + banner_image: form.banner_image, + remove_banner_image: form.remove_banner_image, + introductory_image: form.introductory_image, + remove_introductory_image: form.remove_introductory_image + }.delete_if { |_k, val| val.is_a?(Decidim::ApplicationUploader) } + end + end + end + end +end diff --git a/decidim-elections/app/controllers/concerns/decidim/votings/needs_voting.rb b/decidim-elections/app/controllers/concerns/decidim/votings/needs_voting.rb new file mode 100644 index 0000000000000..8c61df4c8066a --- /dev/null +++ b/decidim-elections/app/controllers/concerns/decidim/votings/needs_voting.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # This module, when injected into a controller, ensures there's a + # voting available and deducts it from the context. + module NeedsVoting + def self.enhance_controller(instance_or_module) + instance_or_module.class_eval do + helper_method :current_voting + end + end + + def self.included(base) + base.include Decidim::NeedsOrganization, InstanceMethods + + enhance_controller(base) + end + + module InstanceMethods + # Public: Finds the current Voting given this controller's + # context. + # + # Returns the current Voting. + def current_voting + @current_voting ||= detect_voting + end + + alias current_participatory_space current_voting + + private + + def detect_voting + request.env["current_voting"] || + organization_votings.find_by(slug: params[:voting_slug] || params[:slug]) + end + + def organization_votings + @organization_votings ||= OrganizationVotings.new(current_organization).query + end + end + end + end +end diff --git a/decidim-elections/app/controllers/concerns/decidim/votings/orderable.rb b/decidim-elections/app/controllers/concerns/decidim/votings/orderable.rb new file mode 100644 index 0000000000000..49d6bc8775fb3 --- /dev/null +++ b/decidim-elections/app/controllers/concerns/decidim/votings/orderable.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module Decidim + module Votings + # Common logic to ordering resources + module Orderable + extend ActiveSupport::Concern + + included do + include Decidim::Orderable + + private + + # Available orders based on enabled settings + def available_orders + %w(random recent) + end + + def reorder(votings) + case order + when "recent" + votings.order_by_most_recent + else + votings.order_randomly(random_seed) + end + end + end + end + end +end diff --git a/decidim-elections/app/controllers/decidim/elections/admin/elections_controller.rb b/decidim-elections/app/controllers/decidim/elections/admin/elections_controller.rb index 30cd69b841182..8dccf03737672 100644 --- a/decidim-elections/app/controllers/decidim/elections/admin/elections_controller.rb +++ b/decidim-elections/app/controllers/decidim/elections/admin/elections_controller.rb @@ -96,7 +96,7 @@ def unpublish private def elections - @elections ||= Election.where(component: current_component) + @elections ||= Election.where(component: current_component).order(start_time: :desc) end def election diff --git a/decidim-elections/app/controllers/decidim/elections/admin/setup_controller.rb b/decidim-elections/app/controllers/decidim/elections/admin/setup_controller.rb deleted file mode 100644 index eb673ee340bf1..0000000000000 --- a/decidim-elections/app/controllers/decidim/elections/admin/setup_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module Elections - module Admin - # This controller allows to setup an election. - class SetupController < Admin::ApplicationController - helper_method :elections, :election, :trustees - - def show - enforce_permission_to :setup, :election, election: election - - @form = form(SetupForm).from_model(election, number_of_trustees: Decidim::Elections.bulletin_board.number_of_trustees) - end - - def update - enforce_permission_to :setup, :election, election: election - - @form = form(SetupForm).from_params(params, election: election, trustee_ids: params[:setup][:trustee_ids]) - SetupElection.call(@form) do - on(:ok) do - flash[:notice] = I18n.t("elections.setup.success", scope: "decidim.elections.admin") - redirect_to elections_path - end - - on(:invalid) do - flash.now[:alert] = I18n.t("elections.setup.invalid", scope: "decidim.elections.admin") - end - end - end - - private - - def elections - @elections ||= Election.where(component: current_component) - end - - def election - @election ||= elections.find_by(id: params[:id]) - end - end - end - end -end diff --git a/decidim-elections/app/controllers/decidim/elections/admin/steps_controller.rb b/decidim-elections/app/controllers/decidim/elections/admin/steps_controller.rb new file mode 100644 index 0000000000000..436626bc0f6ef --- /dev/null +++ b/decidim-elections/app/controllers/decidim/elections/admin/steps_controller.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Admin + # This controller allows to manage the steps of an election. + class StepsController < Admin::ApplicationController + helper StepsHelper + helper_method :elections, :election, :current_step + + def index + enforce_permission_to :read, :steps, election: election + + if current_step_form_class + @form = form(current_step_form_class).instance(election: election) + @form.valid? + end + end + + def update + enforce_permission_to :update, :steps, election: election + redirect_to election_steps_path(election) && return unless params[:id] == current_step + + @form = form(current_step_form_class).from_params(params, election: election) + + current_step_command_class.call(@form) do + on(:ok) do + flash[:notice] = I18n.t("steps.#{current_step}.success", scope: "decidim.elections.admin") + return redirect_to election_steps_path(election) + end + on(:invalid) do |message| + flash.now[:alert] = message || I18n.t("steps.#{current_step}.invalid", scope: "decidim.elections.admin") + end + end + render :index + end + + private + + def current_step_form_class + @current_step_form_class ||= { + "create_election" => SetupForm, + "ready" => BallotBoxForm, + "vote" => BallotBoxForm + }[current_step] + end + + def current_step_command_class + @current_step_command_class ||= { + "create_election" => SetupElection, + "ready" => OpenBallotBox, + "vote" => CloseBallotBox + }[current_step] + end + + def current_step + @current_step ||= election.bb_status || "create_election" + end + + def elections + @elections ||= Election.where(component: current_component) + end + + def election + @election ||= elections.find_by(id: params[:election_id]) + end + end + end + end +end diff --git a/decidim-elections/app/controllers/decidim/elections/trustee_zone/elections_controller.rb b/decidim-elections/app/controllers/decidim/elections/trustee_zone/elections_controller.rb new file mode 100644 index 0000000000000..f55ff72676b80 --- /dev/null +++ b/decidim-elections/app/controllers/decidim/elections/trustee_zone/elections_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module TrusteeZone + # Handles the KeyCeremony for trustee users + class ElectionsController < ::Decidim::ApplicationController + include Decidim::UserProfile + + helper_method :election, :trustee + + def show + enforce_permission_to :view, :election, trustee: trustee + end + + def update + enforce_permission_to :update, :election, trustee: trustee + + UpdateElectionBulletinBoardStatus.call(election, :key_ceremony) do + on(:ok) do + render :update + end + on(:invalid) do + flash[:alert] = I18n.t("elections.update.error", scope: "decidim.elections.trustee_zone") + end + end + end + + private + + def permission_scope + :trustee_zone + end + + def permission_class_chain + [ + Decidim::Elections::Permissions, + Decidim::Permissions + ] + end + + def election + @election ||= Decidim::Elections::Election.find(params[:election_id]) + end + + def trustee + @trustee ||= Decidim::Elections::Trustee.for(current_user) + end + end + end + end +end diff --git a/decidim-elections/app/controllers/decidim/elections/trustee_zone/trustees_controller.rb b/decidim-elections/app/controllers/decidim/elections/trustee_zone/trustees_controller.rb index 4b9c629c9ca06..efd7860aed652 100644 --- a/decidim-elections/app/controllers/decidim/elections/trustee_zone/trustees_controller.rb +++ b/decidim-elections/app/controllers/decidim/elections/trustee_zone/trustees_controller.rb @@ -12,6 +12,8 @@ class TrusteesController < ::Decidim::ApplicationController def show enforce_permission_to :view, :trustee, trustee: trustee + + trustee.name ||= current_user.name end def update diff --git a/decidim-elections/app/controllers/decidim/elections/votes_controller.rb b/decidim-elections/app/controllers/decidim/elections/votes_controller.rb index 2c740b522b336..3ed59b482160e 100644 --- a/decidim-elections/app/controllers/decidim/elections/votes_controller.rb +++ b/decidim-elections/app/controllers/decidim/elections/votes_controller.rb @@ -13,9 +13,24 @@ class VotesController < Decidim::Elections::ApplicationController delegate :count, to: :questions, prefix: true def new + @form = form(Voter::EncryptedVoteForm).instance(election: election) redirect_to(return_path, alert: t("votes.messages.not_allowed", scope: "decidim.elections")) unless booth_mode end + def cast + @form = form(Voter::EncryptedVoteForm).from_params(params, election: election) + return render :cast_success if preview? + + Voter::CastVote.call(@form) do + on(:ok) do + render :cast_success + end + on(:invalid) do + render :cast_failed + end + end + end + private def booth_mode @@ -45,6 +60,10 @@ def election def questions @questions ||= election.questions.includes(:answers).order(weight: :asc, id: :asc) end + + def preview? + booth_mode == :preview + end end end end diff --git a/decidim-elections/app/controllers/decidim/votings/admin/votings_controller.rb b/decidim-elections/app/controllers/decidim/votings/admin/votings_controller.rb index 313a6394e9e33..6a8bc33f75114 100644 --- a/decidim-elections/app/controllers/decidim/votings/admin/votings_controller.rb +++ b/decidim-elections/app/controllers/decidim/votings/admin/votings_controller.rb @@ -36,63 +36,47 @@ def create end def edit - # enforce_permission_to :update, :voting, voting: voting - # @form = form(VotingForm).from_model(voting) + enforce_permission_to :update, :voting, voting: current_voting + @form = form(VotingForm).from_model(current_voting) end def update - # enforce_permission_to :update, :voting, voting: voting - # @form = form(VotingForm).from_params(params) - # - # UpdateVoting.call(@form, voting) do - # on(:ok) do - # flash[:notice] = I18n.t("votings.update.success", scope: "decidim.votings.admin") - # redirect_to votings_path - # end - # - # on(:invalid) do - # flash.now[:alert] = I18n.t("votings.update.invalid", scope: "decidim.votings.admin") - # render action: "edit" - # end - # end - end + enforce_permission_to :update, :voting, voting: current_voting + @form = form(VotingForm).from_params(params, voting_id: current_voting.id) + + UpdateVoting.call(current_voting, @form) do + on(:ok) do + flash[:notice] = I18n.t("votings.update.success", scope: "decidim.votings.admin") + redirect_to edit_voting_path(voting) + end - def destroy - # enforce_permission_to :delete, :voting, voting: voting - # - # DestroyVoting.call(voting, current_user) do - # on(:ok) do - # flash[:notice] = I18n.t("votings.destroy.success", scope: "decidim.votings.admin") - # end - # - # on(:invalid) do - # flash.now[:alert] = I18n.t("votings.destroy.invalid", scope: "decidim.votings.admin") - # end - # end - # - # redirect_to votings_path + on(:invalid) do + flash.now[:alert] = I18n.t("votings.update.invalid", scope: "decidim.votings.admin") + render action: "edit" + end + end end def publish - # enforce_permission_to :publish, :voting, voting: voting - # - # PublishVoting.call(voting, current_user) do - # on(:ok) do - # flash[:notice] = I18n.t("admin.votings.publish.success", scope: "decidim.votings") - # redirect_to votings_path - # end - # end + enforce_permission_to :publish, :voting, voting: current_voting + + PublishVoting.call(current_voting, current_user) do + on(:ok) do + flash[:notice] = I18n.t("votings.publish.success", scope: "decidim.votings.admin") + redirect_to edit_voting_path(voting) + end + end end def unpublish - # enforce_permission_to :unpublish, :voting, voting: voting - # - # UnpublishVoting.call(voting, current_user) do - # on(:ok) do - # flash[:notice] = I18n.t("admin.votings.unpublish.success", scope: "decidim.votings") - # redirect_to votings_path - # end - # end + enforce_permission_to :unpublish, :voting, voting: current_voting + + UnpublishVoting.call(current_voting, current_user) do + on(:ok) do + flash[:notice] = I18n.t("votings.unpublish.success", scope: "decidim.votings.admin") + redirect_to edit_voting_path(voting) + end + end end private @@ -105,8 +89,10 @@ def collection @collection ||= OrganizationVotings.new(current_user.organization).query end - def voting - @voting ||= votings.find_by(id: params[:id]) + def current_voting + @current_voting ||= collection.where(slug: params[:slug]).or( + collection.where(id: params[:slug]) + ).first end end end diff --git a/decidim-elections/app/controllers/decidim/votings/application_controller.rb b/decidim-elections/app/controllers/decidim/votings/application_controller.rb new file mode 100644 index 0000000000000..b1cdb0ec47f17 --- /dev/null +++ b/decidim-elections/app/controllers/decidim/votings/application_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # A controller that holds the logic to show votings in a + # public layout. + class ApplicationController < Decidim::ApplicationController + include NeedsPermission + + register_permissions(::Decidim::Votings::ApplicationController, + Decidim::Votings::Permissions, + Decidim::Admin::Permissions, + Decidim::Permissions) + + private + + def permission_class_chain + ::Decidim.permissions_registry.chain_for(::Decidim::Votings::ApplicationController) + end + + def permission_scope + :public + end + end + end +end diff --git a/decidim-elections/app/controllers/decidim/votings/votings_controller.rb b/decidim-elections/app/controllers/decidim/votings/votings_controller.rb new file mode 100644 index 0000000000000..d061e2ec114a4 --- /dev/null +++ b/decidim-elections/app/controllers/decidim/votings/votings_controller.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # A controller that holds the logic to show votings in a + # public layout. + class VotingsController < Decidim::Votings::ApplicationController + layout "layouts/decidim/voting", only: :show + + include ParticipatorySpaceContext + include NeedsVoting + include FilterResource + include Paginable + include Decidim::Votings::Orderable + + helper_method :collection, :votings, :finished_votings, :active_votings, :filter + + helper Decidim::FiltersHelper + helper Decidim::OrdersHelper + helper Decidim::SanitizeHelper + helper Decidim::PaginateHelper + helper Decidim::IconHelper + helper Decidim::WidgetUrlsHelper + helper Decidim::ResourceHelper + + def index + enforce_permission_to :read, :votings + end + + def show + enforce_permission_to :read, :voting, voting: current_voting + end + + private + + def current_participatory_space_manifest + @current_participatory_space_manifest ||= Decidim.find_participatory_space_manifest(:votings) + end + + def votings + @votings = search.results + @votings = reorder(@votings) + @votings = paginate(@votings) + end + + alias collection votings + + def search_klass + VotingSearch + end + + def default_filter_params + { + search_text: "", + state: "all" + } + end + + def context_params + { + organization: current_organization, + current_user: current_user + } + end + end + end +end diff --git a/decidim-elections/app/forms/decidim/elections/admin/ballot_box_form.rb b/decidim-elections/app/forms/decidim/elections/admin/ballot_box_form.rb new file mode 100644 index 0000000000000..6d9bb2a14dafe --- /dev/null +++ b/decidim-elections/app/forms/decidim/elections/admin/ballot_box_form.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Admin + # This class holds a form to open and close a ballot box. + class BallotBoxForm < Decidim::Form + validate do + validations.each do |message, t_args, valid| + errors.add(message, I18n.t("steps.#{current_step}.errors.#{message}", **t_args, scope: "decidim.elections.admin")) unless valid + end + end + + def validations + @validations ||= if current_step == "ready" + [ + [:time_before, + { start_time: I18n.l(election.start_time, format: :long), + hours: Decidim::Elections.open_ballot_box_maximum_hours_before_start }, + election.maximum_hours_before_start?] + ].freeze + else + [ + [:time_after, { end_time: I18n.l(election.end_time, format: :long) }, election.finished?] + ].freeze + end + end + + def messages + @messages ||= validations.map do |message, t_args, _valid| + [message, I18n.t("steps.#{current_step}.requirements.#{message}", **t_args, scope: "decidim.elections.admin")] + end.to_h + end + + def current_step + @current_step ||= election.bb_status + end + + def election + @election ||= context[:election] + end + + def bulletin_board + @bulletin_board ||= context[:bulletin_board] || Decidim::Elections.bulletin_board + end + end + end + end +end diff --git a/decidim-elections/app/forms/decidim/elections/admin/setup_form.rb b/decidim-elections/app/forms/decidim/elections/admin/setup_form.rb index 283e416475f18..33f585ca828db 100644 --- a/decidim-elections/app/forms/decidim/elections/admin/setup_form.rb +++ b/decidim-elections/app/forms/decidim/elections/admin/setup_form.rb @@ -5,67 +5,69 @@ module Elections module Admin # This class holds a form to setup elections from Decidim's admin panel. class SetupForm < Decidim::Form - include TranslatableAttributes + mimic :setup attribute :trustee_ids, Array[Integer] - validate :check_election_is_valid - - def map_model(model) - @election = model - @trustees = Decidim::Elections::Trustees::ByParticipatorySpace.new(election.component.participatory_space).to_a.sample(number_of_trustees).sort_by(&:id) + validate do + validations.each do |message, t_args, valid| + errors.add(message, I18n.t("steps.create_election.errors.#{message}", **t_args, scope: "decidim.elections.admin")) unless valid + end + end - self.trustee_ids = @trustees.pluck(:id) + def trustee_ids + choose_random_trustees end def trustees - @trustees = Decidim::Elections::Trustees::ByParticipatorySpace.new(election.component.participatory_space) - - self.trustee_ids = @trustees.pluck(:id) - - @trustees = Decidim::Elections::Trustees::ByParticipatorySpaceTrusteeIds.new(trustee_ids).to_a.sort_by(&:id) - @trustees + ids = trustee_ids + @trustees ||= Decidim::Elections::Trustees::ByParticipatorySpaceTrusteeIds.new(ids).to_a.sort_by(&:id) end - def number_of_trustees - Decidim::Elections.bulletin_board.number_of_trustees + def validations + @validations ||= [ + [:minimum_questions, {}, election.questions.any?], + [:minimum_answers, {}, election.minimum_answers?], + [:max_selections, {}, election.valid_questions?], + [:published, {}, election.published_at.present?], + [:time_before, { hours: Decidim::Elections.setup_minimum_hours_before_start }, election.minimum_hours_before_start?], + [:trustees_number, { number: bulletin_board.number_of_trustees }, participatory_space_trustees_with_public_key.size >= bulletin_board.number_of_trustees] + ].freeze end def messages - { published: I18n.t("admin.setup.requirements.published", scope: "decidim.elections"), - time_before: I18n.t("admin.setup.requirements.time_before", scope: "decidim.elections"), - minimum_questions: I18n.t("admin.setup.requirements.minimum_questions", scope: "decidim.elections"), - minimum_answers: I18n.t("admin.setup.requirements.minimum_answers", scope: "decidim.elections"), - max_selections: I18n.t("admin.setup.requirements.max_selections", scope: "decidim.elections"), - trustees_quorum: I18n.t("admin.setup.requirements.trustees_quorum", scope: "decidim.elections", quorum: Decidim::Elections.bulletin_board.quorum) } + @messages ||= validations.map do |message, t_args, _valid| + [message, I18n.t("steps.create_election.requirements.#{message}", **t_args, scope: "decidim.elections.admin")] + end.to_h + end + + def participatory_space_trustees + @participatory_space_trustees ||= Decidim::Elections::Trustees::ByParticipatorySpace.new(election.component.participatory_space).to_a end def election @election ||= context[:election] end - def check_election_is_valid - errors.add("published", I18n.t("admin.setup.errors.published", scope: "decidim.elections")) if election.published_at.blank? - errors.add("time_before", I18n.t("admin.setup.errors.time_before", scope: "decidim.elections")) unless election.minimum_three_hours_before_start? - errors.add("minimum_questions", I18n.t("admin.setup.errors.minimum_questions", scope: "decidim.elections")) if election.questions.empty? - errors.add("minimum_answers", I18n.t("admin.setup.errors.minimum_answers", scope: "decidim.elections")) unless election.minimum_answers? - errors.add("max_selections", I18n.t("admin.setup.errors.max_selections", scope: "decidim.elections")) unless election.valid_questions? - errors.add("trustees_quorum", I18n.t("admin.setup.errors.trustees_quorum", scope: "decidim.elections")) unless trustees_satisfy_quorum - check_trustees_public_keys + def bulletin_board + @bulletin_board ||= context[:bulletin_board] || Decidim::Elections.bulletin_board end - def trustees_satisfy_quorum - return if Decidim::Elections.bulletin_board.quorum.blank? + private - trustees.size >= Decidim::Elections.bulletin_board.quorum + def choose_random_trustees + return @trustee_ids if @trustee_ids.any? || defined?(@trustees) + + @trustees = if participatory_space_trustees_with_public_key.count >= bulletin_board.number_of_trustees + participatory_space_trustees_with_public_key.sample(bulletin_board.number_of_trustees) + else + [] + end + @trustee_ids = @trustees.pluck(:id) end - def check_trustees_public_keys - trustees.each do |trustee| - if trustee.public_key.blank? - errors.add("trustee_id_#{trustee.id}", "#{trustee.user.name} #{I18n.t("admin.setup.errors.trustee_public_key", scope: "decidim.elections")}") - end - end + def participatory_space_trustees_with_public_key + @participatory_space_trustees_with_public_key ||= participatory_space_trustees.filter { |trustee| trustee.public_key.present? } end end end diff --git a/decidim-elections/app/forms/decidim/elections/trustee_zone/trustee_form.rb b/decidim-elections/app/forms/decidim/elections/trustee_zone/trustee_form.rb index 406f8219fafb7..ef601a46fdafc 100644 --- a/decidim-elections/app/forms/decidim/elections/trustee_zone/trustee_form.rb +++ b/decidim-elections/app/forms/decidim/elections/trustee_zone/trustee_form.rb @@ -7,11 +7,13 @@ module TrusteeZone class TrusteeForm < Decidim::Form mimic :trustee + attribute :name, String attribute :public_key, String - validates :public_key, presence: true - validate :dont_change_public_key + validates :name, :public_key, presence: true + validate :dont_change_data - def dont_change_public_key + def dont_change_data + errors.add :name, :cant_be_changed if trustee.name.present? errors.add :public_key, :cant_be_changed if trustee.public_key.present? end diff --git a/decidim-elections/app/forms/decidim/elections/voter/encrypted_vote_form.rb b/decidim-elections/app/forms/decidim/elections/voter/encrypted_vote_form.rb new file mode 100644 index 0000000000000..2e454449bcf71 --- /dev/null +++ b/decidim-elections/app/forms/decidim/elections/voter/encrypted_vote_form.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Voter + # This class holds the data to cast a vote. + class EncryptedVoteForm < Decidim::Form + attribute :encrypted_vote, String + attribute :encrypted_vote_hash, String + + validates :encrypted_vote, :encrypted_vote_hash, :current_user, :election, presence: true + validate :hash_is_valid + + delegate :id, to: :election, prefix: true + + def election_unique_id + @election_unique_id ||= Decidim::BulletinBoard::MessageIdentifier.unique_election_id(bulletin_board.authority_slug, election_id) + end + + # Public: computes a unique id for the voter/election pair. + def voter_id + @voter_id ||= Digest::SHA256.hexdigest([current_user.created_at, current_user.id, election.id, bulletin_board.authority_slug].join(".")) + end + + # Public: returns the associated election for the vote. + def election + @election ||= context.election + end + + def bulletin_board + @bulletin_board ||= context[:bulletin_board] || Decidim::Elections.bulletin_board + end + + private + + def current_user + @current_user ||= context[:current_user] + end + + # Private: check if the hash sent by the browser is correct. + def hash_is_valid + return if encrypted_vote.blank? || encrypted_vote_hash.blank? + + errors.add(:encrypted_vote_hash, :invalid) if Digest::SHA256.hexdigest(encrypted_vote) != encrypted_vote_hash + end + end + end + end +end diff --git a/decidim-elections/app/forms/decidim/votings/admin/voting_form.rb b/decidim-elections/app/forms/decidim/votings/admin/voting_form.rb index 7b6c7c33f1b80..506703dbf9aa6 100644 --- a/decidim-elections/app/forms/decidim/votings/admin/voting_form.rb +++ b/decidim-elections/app/forms/decidim/votings/admin/voting_form.rb @@ -13,11 +13,10 @@ class VotingForm < Decidim::Form attribute :start_time, Decidim::Attributes::TimeWithZone attribute :end_time, Decidim::Attributes::TimeWithZone attribute :scope_id, Integer + attribute :remove_banner_image attribute :banner_image, String + attribute :remove_introductory_image attribute :introductory_image, String - # attribute :attachment, AttachmentForm - # attribute :photos, Array[String] - # attribute :add_photos, Array validates :title, translatable_presence: true validates :description, translatable_presence: true @@ -27,7 +26,6 @@ class VotingForm < Decidim::Form validates :end_time, presence: true, date: { after: :start_time } validates :banner_image, passthru: { to: Decidim::Votings::Voting } validates :introductory_image, passthru: { to: Decidim::Votings::Voting } - # validate :notify_missing_attachment_if_errored validates :scope, presence: true, if: proc { |object| object.scope_id.present? } @@ -55,15 +53,6 @@ def slug_uniqueness errors.add(:slug, :taken) end - - # This method will add an error to the `photos` field only if there's - # any error in any other field. This is needed because when the form has - # an error, the attachment is lost, so we need a way to inform the user of - # this problem. - def notify_missing_attachment_if_errored - errors.add(:attachment, :needs_to_be_reattached) if errors.any? && attachment.present? - errors.add(:add_photos, :needs_to_be_reattached) if errors.any? && add_photos.present? - end end end end diff --git a/decidim-elections/app/helpers/decidim/elections/admin/steps_helper.rb b/decidim-elections/app/helpers/decidim/elections/admin/steps_helper.rb new file mode 100644 index 0000000000000..ab15233f865df --- /dev/null +++ b/decidim-elections/app/helpers/decidim/elections/admin/steps_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Decidim + module Elections + module Admin + # Custom helpers for election steps on admin dashboard. + # + module StepsHelper + def steps(current_step) + step_class = "text-success" + (["create_election"] + Decidim::Elections::Election.bb_statuses.keys).map do |step| + if step == current_step + step_class = "text-muted" + [step, "text-warning"] + else + [step, step_class] + end + end + end + end + end + end +end diff --git a/decidim-elections/app/helpers/decidim/votings/votings_helper.rb b/decidim-elections/app/helpers/decidim/votings/votings_helper.rb new file mode 100644 index 0000000000000..38fe056141b2a --- /dev/null +++ b/decidim-elections/app/helpers/decidim/votings/votings_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Decidim + module Votings + module VotingsHelper + # Returns options for state filter selector. + def options_for_state_filter + [ + ["all", t("votings.filters.all", scope: "decidim.votings")], + ["active", t("votings.filters.active", scope: "decidim.votings")], + ["upcoming", t("votings.filters.upcoming", scope: "decidim.votings")], + ["finished", t("votings.filters.finished", scope: "decidim.votings")] + ] + end + end + end +end diff --git a/decidim-elections/app/models/decidim/elections/election.rb b/decidim-elections/app/models/decidim/elections/election.rb index dd029f321b889..a61db81ce9a69 100644 --- a/decidim-elections/app/models/decidim/elections/election.rb +++ b/decidim-elections/app/models/decidim/elections/election.rb @@ -64,11 +64,18 @@ def ongoing? started? && !finished? end - # Public: Checks if the election start_time is minimum 3 hours later than the present time + # Public: Checks if the election start_time is minimum some hours later than the present time # # Returns a boolean. - def minimum_three_hours_before_start? - start_time > (Time.zone.at(3.hours.from_now)) + def minimum_hours_before_start? + start_time > (Time.zone.at(Decidim::Elections.setup_minimum_hours_before_start.hours.from_now)) + end + + # Public: Checks if the election start_time is maximum some hours before than the present time + # + # Returns a boolean. + def maximum_hours_before_start? + start_time < (Time.zone.at(Decidim::Elections.open_ballot_box_maximum_hours_before_start.hours.from_now)) end # Public: Checks if the number of answers are minimum 2 for each question @@ -112,6 +119,10 @@ def voting_period_status end end + def trustee_action_required? + bb_key_ceremony? || bb_tally? + end + # Public: Checks if the election has a blocked_at value # # Returns a boolean. diff --git a/decidim-elections/app/models/decidim/elections/trustee.rb b/decidim-elections/app/models/decidim/elections/trustee.rb index d07472de61b41..334e6516828ce 100644 --- a/decidim-elections/app/models/decidim/elections/trustee.rb +++ b/decidim-elections/app/models/decidim/elections/trustee.rb @@ -17,6 +17,10 @@ def self.trustee?(user) def self.for(user) find_by(user: user) end + + def unique_id + name.parameterize + end end end end diff --git a/decidim-elections/app/models/decidim/elections/vote.rb b/decidim-elections/app/models/decidim/elections/vote.rb new file mode 100644 index 0000000000000..287b0db1a01c0 --- /dev/null +++ b/decidim-elections/app/models/decidim/elections/vote.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Decidim + module Elections + # The data store for a Vote in the Decidim::Election component. It stores the hash computed from the `encrypted_vote` associated to a particular `voter_id`. + class Vote < ApplicationRecord + PENDING_STATUS = "pending" + ALLOWED_STATUS = [PENDING_STATUS].freeze + + belongs_to :election, foreign_key: "decidim_elections_election_id", class_name: "Decidim::Elections::Election" + + validates :voter_id, :encrypted_vote_hash, presence: true + validates :status, inclusion: { in: ALLOWED_STATUS } + end + end +end diff --git a/decidim-elections/app/models/decidim/votings/voting.rb b/decidim-elections/app/models/decidim/votings/voting.rb index 1f32649bc1fc2..26f4bb9d81f6d 100644 --- a/decidim-elections/app/models/decidim/votings/voting.rb +++ b/decidim-elections/app/models/decidim/votings/voting.rb @@ -7,6 +7,7 @@ class Voting < ApplicationRecord include Loggable include Decidim::Participable include Decidim::ParticipatorySpaceResourceable + include Decidim::Randomable include Decidim::Searchable include Decidim::TranslatableResource include Decidim::ScopableParticipatorySpace @@ -19,6 +20,8 @@ class Voting < ApplicationRecord foreign_key: "decidim_organization_id", class_name: "Decidim::Organization" + has_many :components, as: :participatory_space, dependent: :destroy + validates :slug, uniqueness: { scope: :organization } validates :slug, presence: true, format: { with: Decidim::Votings::Voting.slug_format } @@ -28,6 +31,37 @@ class Voting < ApplicationRecord validates_upload :introductory_image mount_uploader :introductory_image, Decidim::BannerImageUploader + scope :upcoming, -> { published.where("start_time > ?", Time.now.utc) } + scope :active, lambda { + published + .where("start_time <= ?", Time.now.utc) + .where("end_time >= ?", Time.now.utc) + } + scope :finished, -> { published.where("end_time < ?", Time.now.utc) } + scope :order_by_most_recent, -> { order(created_at: :desc) } + + def upcoming? + start_time > Time.now.utc + end + + def active? + start_time <= Time.now.utc && end_time >= Time.now.utc + end + + def finished? + end_time < Time.now.utc + end + + def period_status + if finished? + :finished + elsif active? + :ongoing + else + :upcoming + end + end + searchable_fields({ scope_id: :decidim_scope_id, participatory_space: :itself, @@ -47,9 +81,8 @@ def self.log_presenter_class_for(_log) Arel::Nodes::InfixOperation.new("->>", parent.table[:title], Arel::Nodes.build_quoted(I18n.locale.to_s)) end - # should remove this method when we have public views - def self.public_spaces - none + def to_param + slug end end end diff --git a/decidim-elections/app/permissions/decidim/elections/admin/permissions.rb b/decidim-elections/app/permissions/decidim/elections/admin/permissions.rb index 2ad98404bd2e4..fcbc9900a5e4d 100644 --- a/decidim-elections/app/permissions/decidim/elections/admin/permissions.rb +++ b/decidim-elections/app/permissions/decidim/elections/admin/permissions.rb @@ -15,11 +15,16 @@ def permissions when :select allow_if_results end + when :steps + case permission_action.action + when :read, :update + allow! + end when :election case permission_action.action when :create, :read allow! - when :delete, :update, :unpublish, :setup + when :delete, :update, :unpublish allow_if_not_blocked when :publish allow_if_valid_and_not_blocked diff --git a/decidim-elections/app/permissions/decidim/elections/trustee_zone/permissions.rb b/decidim-elections/app/permissions/decidim/elections/trustee_zone/permissions.rb index 3b9281d4d226b..a9f1cdaf21504 100644 --- a/decidim-elections/app/permissions/decidim/elections/trustee_zone/permissions.rb +++ b/decidim-elections/app/permissions/decidim/elections/trustee_zone/permissions.rb @@ -8,7 +8,7 @@ def permissions return permission_action unless permission_action.scope == :trustee_zone case permission_action.subject - when :trustee + when :trustee, :election toggle_allow(trustee_for_user?) if [:view, :update].member?(permission_action.action) when :user allow! if permission_action.action == :update_profile diff --git a/decidim-elections/app/permissions/decidim/votings/admin/permissions.rb b/decidim-elections/app/permissions/decidim/votings/admin/permissions.rb index 5e829439f65ad..09522aa42a8ed 100644 --- a/decidim-elections/app/permissions/decidim/votings/admin/permissions.rb +++ b/decidim-elections/app/permissions/decidim/votings/admin/permissions.rb @@ -65,7 +65,7 @@ def allowed_voting_action? toggle_allow(user.admin?) if permission_action.action == :read when :voting case permission_action.action - when :read, :create, :publish + when :read, :create, :publish, :unpublish allow! when :update, :preview toggle_allow(voting.present?) diff --git a/decidim-elections/app/presenters/decidim/elections/admin_log/election_presenter.rb b/decidim-elections/app/presenters/decidim/elections/admin_log/election_presenter.rb index eb1f8c0dd5201..f4bdd1d4bb19a 100644 --- a/decidim-elections/app/presenters/decidim/elections/admin_log/election_presenter.rb +++ b/decidim-elections/app/presenters/decidim/elections/admin_log/election_presenter.rb @@ -29,7 +29,7 @@ def i18n_labels_scope def action_string case action - when "publish", "unpublish" + when "publish", "unpublish", "setup", "open_ballot_box", "close_ballot_box" "decidim.elections.admin_log.election.#{action}" else super diff --git a/decidim-elections/app/queries/decidim/elections/elections_finished_to_close.rb b/decidim-elections/app/queries/decidim/elections/elections_finished_to_close.rb new file mode 100644 index 0000000000000..2190e45ea50cf --- /dev/null +++ b/decidim-elections/app/queries/decidim/elections/elections_finished_to_close.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Decidim + module Elections + # A class used to find elections finished to close their ballot boxes + class ElectionsFinishedToClose < Rectify::Query + # Syntactic sugar to initialize the class and return the queried objects. + def self.for + new.query + end + + # Finds the Elections that should be closed. + def query + Decidim::Elections::Election.bb_vote + .where("end_time <= ?", Time.current) + end + end + end +end diff --git a/decidim-elections/app/queries/decidim/elections/elections_ready_to_open.rb b/decidim-elections/app/queries/decidim/elections/elections_ready_to_open.rb new file mode 100644 index 0000000000000..9a65850df634d --- /dev/null +++ b/decidim-elections/app/queries/decidim/elections/elections_ready_to_open.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Decidim + module Elections + # A class used to find elections ready and near to start to open their ballot boxes + class ElectionsReadyToOpen < Rectify::Query + # Syntactic sugar to initialize the class and return the queried objects. + def self.for + new.query + end + + # Finds the Elections that should be opened. + def query + Decidim::Elections::Election.bb_ready + .where("start_time <= ?", minimum_start_time) + end + + private + + def minimum_start_time + @minimum_start_time ||= Decidim::Elections.setup_minimum_hours_before_start.hours.from_now + end + end + end +end diff --git a/decidim-elections/app/services/decidim/votings/voting_search.rb b/decidim-elections/app/services/decidim/votings/voting_search.rb new file mode 100644 index 0000000000000..d39c5da8d0755 --- /dev/null +++ b/decidim-elections/app/services/decidim/votings/voting_search.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # Service that encapsulates all logic related to filtering votings. + class VotingSearch < Searchlight::Search + # Public: Initializes the service. + # page - The page number to paginate the results. + # per_page - The number of proposals to return per page. + def initialize(options = {}) + super(options) + end + + def base_query + Decidim::Votings::Voting.where(organization: options[:organization]).published + end + + # Handle the search_text filter + def search_search_text + query + .where("title->>'#{current_locale}' ILIKE ?", "%#{search_text}%") + .or( + query.where("description->>'#{current_locale}' ILIKE ?", "%#{search_text}%") + ) + end + + # Handle the state filter + def search_state + case state + when "active" + query.active + when "upcoming" + query.upcoming + when "finished" + query.finished + else # Assume all + query + end + end + + private + + def current_locale + I18n.locale.to_s + end + end + end +end diff --git a/decidim-elections/app/views/decidim/elections/admin/elections/index.html.erb b/decidim-elections/app/views/decidim/elections/admin/elections/index.html.erb index efe20da09b44a..0f7e877516316 100644 --- a/decidim-elections/app/views/decidim/elections/admin/elections/index.html.erb +++ b/decidim-elections/app/views/decidim/elections/admin/elections/index.html.erb @@ -15,6 +15,7 @@ <%= t("models.election.fields.title", scope: "decidim.elections") %> <%= t("models.election.fields.start_time", scope: "decidim.elections") %> <%= t("models.election.fields.end_time", scope: "decidim.elections") %> + <%= t("models.election.fields.bb_status", scope: "decidim.elections") %> <%= t("actions.title", scope: "decidim.elections") %> @@ -34,15 +35,14 @@ <%= l election.end_time, format: :long %> <% end %> + <%= t("steps.#{election.bb_status || "create_election"}.title", scope: "decidim.elections.admin") %> <%= icon_link_to "eye", resource_locator(election).path, t("actions.preview", scope: "decidim.elections"), class: "action-icon--preview", target: :blank %> <%= icon_link_to "list", election_questions_path(election), t("actions.manage_questions", scope: "decidim.elections"), class: "action-icon--manage-questions" %> - <% if allowed_to? :setup, :election, election: election %> - <%= icon_link_to "power-standby", url_for(action: :show, id: election, controller: "setup"), t("actions.setup", scope: "decidim.elections"), class: "action-icon--setup-election" %> - <% else %> - <%= icon "power-standby", class: "action-icon action-icon--disabled", role: "img" %> + <% if allowed_to? :read, :steps, election: election %> + <%= icon_link_to "project", election_steps_path(election), t("actions.manage_steps", scope: "decidim.elections"), class: "action-icon--manage-steps" %> <% end %> <% if allowed_to? :update, :election, election: election %> diff --git a/decidim-elections/app/views/decidim/elections/admin/setup/_form.html.erb b/decidim-elections/app/views/decidim/elections/admin/setup/_form.html.erb deleted file mode 100644 index 4e79c76c746e8..0000000000000 --- a/decidim-elections/app/views/decidim/elections/admin/setup/_form.html.erb +++ /dev/null @@ -1,35 +0,0 @@ -

-
-

<%= title %>

-
- -
- <% @form.valid? %> - <% @form.messages.each do |key, value| %> - <% if form.object.errors.include?(key) %> -

<%= icon "x", role: "img", "aria-hidden": true %> <%= form.object.errors.messages[key][0].html_safe %>

- <% else %> -

<%= icon "check", role: "img", "aria-hidden": true %> <%= value.html_safe %>

- <% end %> - <% end %> -

- - <% if @form.trustees.any? %> -

Selected trustees

- - <% end %> -
-
diff --git a/decidim-elections/app/views/decidim/elections/admin/setup/show.html.erb b/decidim-elections/app/views/decidim/elections/admin/setup/show.html.erb deleted file mode 100644 index 7a9ce1f39068e..0000000000000 --- a/decidim-elections/app/views/decidim/elections/admin/setup/show.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%= decidim_form_for(@form, html: { class: "form setup_election" }) do |f| %> - <%= render partial: "form", object: f, locals: { title: t(".title") } %> - -
- <%= f.submit t(".setup"), disabled: @form.invalid? %> -
-<% end %> diff --git a/decidim-elections/app/views/decidim/elections/admin/setup/update.html.erb b/decidim-elections/app/views/decidim/elections/admin/setup/update.html.erb deleted file mode 100644 index 22ad9a204cd72..0000000000000 --- a/decidim-elections/app/views/decidim/elections/admin/setup/update.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%= decidim_form_for(@form, html: { class: "form setup_election" }) do |f| %> - <%= render partial: "form", object: f, locals: { title: t(".title") } %> - -
- <%= f.submit t(".setup") %> -
-<% end %> diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/_create_election.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/_create_election.html.erb new file mode 100644 index 0000000000000..ff6c2e60040b2 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/_create_election.html.erb @@ -0,0 +1,42 @@ +
+
+

<%= t(".title") %>

+
+ +
+ + +

<%= t(".trustees") %>

+ +
+
+ +
+ <%= f.submit t(".submit"), disabled: form.invalid? %> +
diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/_key_ceremony.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/_key_ceremony.html.erb new file mode 100644 index 0000000000000..0ff7d81ba65fd --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/_key_ceremony.html.erb @@ -0,0 +1,16 @@ +
+
+

<%= t(".title") %>

+
+ +
+

<%= t(".trustees") %>

+ +
+
diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/_ready.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/_ready.html.erb new file mode 100644 index 0000000000000..dd27c8b2cc79d --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/_ready.html.erb @@ -0,0 +1,20 @@ +
+
+

<%= t(".title") %>

+
+ +
+ <% form.messages.each do |key, value| %> + <% if form.errors.include?(key) %> +

<%= form.errors.messages[key][0].html_safe %>

+ <% else %> +

<%= value.html_safe %>

+ <% end %> + <% end %> +
+
+ +
+ <%= f.hidden_field :current_step %> + <%= f.submit t(".submit"), disabled: form.invalid? %> +
diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/_tally.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/_tally.html.erb new file mode 100644 index 0000000000000..d7be6b9ab8b41 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/_tally.html.erb @@ -0,0 +1,9 @@ +
+
+

<%= t(".title") %>

+
+ +
+ Tally +
+
diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/_vote.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/_vote.html.erb new file mode 100644 index 0000000000000..ee131aa467878 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/_vote.html.erb @@ -0,0 +1,19 @@ +
+
+

<%= t(".title") %>

+
+ +
+ <% form.messages.each do |key, value| %> + <% if form.errors.include?(key) %> +

<%= form.errors.messages[key][0].html_safe %>

+ <% else %> +

<%= value.html_safe %>

+ <% end %> + <% end %> +
+
+ +
+ <%= f.submit t(".submit"), disabled: form.invalid? %> +
diff --git a/decidim-elections/app/views/decidim/elections/admin/steps/index.html.erb b/decidim-elections/app/views/decidim/elections/admin/steps/index.html.erb new file mode 100644 index 0000000000000..16470731187e8 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/admin/steps/index.html.erb @@ -0,0 +1,24 @@ +
+
+

+ <%= t(".title") %> +

+
+ +
+ <% steps(current_step).each_with_index do |step, i| %> + <% if i > 0 %> > <% end %> + <%= t("steps.#{step.first}.title", scope: "decidim.elections.admin") %> + <% end %> +
+
+ +<% if @form %> + <%= decidim_form_for(@form, url: election_step_path(election, current_step), method: :patch, html: { class: "form #{current_step}" }) do |f| %> + <%= render partial: current_step.to_s, locals: { form: @form, f: f } %> + <% end %> +<% else %> +
+ <%= render partial: current_step.to_s %> +
+<% end %> diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_backup_modal.html.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_backup_modal.html.erb new file mode 100644 index 0000000000000..e7b2e77b29f74 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_backup_modal.html.erb @@ -0,0 +1,24 @@ +
+
+

<%= t(".title", election: translated_attribute(election.title)) %>

+
+ +
+
+
+
+

<%= t(".description") %>

+
+
+
+
+ +
+
+ +
+
+
diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_key_ceremony_steps.html.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_key_ceremony_steps.html.erb new file mode 100644 index 0000000000000..a54a23194d064 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_key_ceremony_steps.html.erb @@ -0,0 +1,44 @@ +

<%= t(".title", election: translated_attribute(election.title)) %>

+

<%= t(".description") %>

+
+

<%= t(".process_warning") %>

+
+ +
+ + + + + + + + + <% %w(create_election key_ceremony.step_1 key_ceremony.joint_election_key).each do |step| %> + + + + + <% end %> + +
<%= t(".list.task") %><%= t(".list.status") %>
<%= t(".keys.#{step}") %>" class="step_status" data-step-status="pending"> + <%= t(".status.pending") %> + <%= t(".status.processing") %> + <%= t(".status.completed") %> +
+ <%= render("backup_modal") %> + <%= render("restore_modal") %> +
+ + + +<%= link_to trustee_path, class: "back button hide" do %> + <%= icon "chevron-left", class: "icon--small", role: "img", "aria-hidden": true %> + <%= t(".back") %> +<% end %> + +
+ <%= link_to "", trustee_election_elections_path(election), method: :put, class: "hide" %> +
diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_restore_modal.html.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_restore_modal.html.erb new file mode 100644 index 0000000000000..d03fda872978b --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/_restore_modal.html.erb @@ -0,0 +1,24 @@ +
+
+

<%= t(".title", election: translated_attribute(election.title)) %>

+
+ +
+
+
+
+

<%= t(".description") %>

+
+
+
+
+ +
+
+ +
+
+
diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/elections/show.html.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/show.html.erb new file mode 100644 index 0000000000000..49421fae966e9 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/show.html.erb @@ -0,0 +1,21 @@ +<%= stylesheet_link_tag "decidim/elections/trustee_zone", media: "all" %> + +
+ +
+
+ +
+ <%= render("key_ceremony_steps") %> +
+
+ +<%= javascript_include_tag "decidim/elections/trustee_zone/key_ceremony", integrity: true %> diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/elections/update.js.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/update.js.erb new file mode 100644 index 0000000000000..b0e107467bbba --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/elections/update.js.erb @@ -0,0 +1,5 @@ +$(".key-ceremony-result").html( + `
+ <%= j(I18n.t("elections.update.success", scope: "decidim.elections.trustee_zone", status: election.bb_status)) %> +
` +) diff --git a/decidim-elections/app/views/decidim/elections/trustee_zone/trustees/show.html.erb b/decidim-elections/app/views/decidim/elections/trustee_zone/trustees/show.html.erb index 7db496d5b28b6..82f1369227a0d 100644 --- a/decidim-elections/app/views/decidim/elections/trustee_zone/trustees/show.html.erb +++ b/decidim-elections/app/views/decidim/elections/trustee_zone/trustees/show.html.erb @@ -7,7 +7,8 @@
<%= decidim_form_for(trustee, html: { class: "form edit_trustee" }) do |f| %> - <%= f.hidden_field :id %> + <%= f.hidden_field :unique_id %> + <%= f.hidden_field :name %> <%= f.hidden_field :public_key %> <%= f.submit "", id: "submit_identification_public_key", class: "hide" %> <% end %> @@ -66,16 +67,26 @@ <%= t(".elections.list.election") %> - <%= t(".elections.list.status") %> <%= t(".elections.list.voting_period") %> + <%= t(".elections.list.bb_status") %> + <%= t(".elections.list.action_required.name") %> <% trustee.elections.each do |election| %> <%= present(election).title %> - <%= election.voting_period_status %> <%= l(election.start_time, format: :decidim_short) %> - <%= l(election.end_time, format: :decidim_short) %> + <%= election.bb_status %> + + <% if election.trustee_action_required? %> + <%= link_to trustee_election_elections_path(election) do %> + <%= t(".elections.list.action_required.true") %> + <% end %> + <% else %> + <%= t(".elections.list.action_required.false") %> + <% end %> + <% end %> diff --git a/decidim-elections/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb b/decidim-elections/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb index 5fdcaca08991f..1cdf17c5df0a7 100644 --- a/decidim-elections/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb +++ b/decidim-elections/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb @@ -2,23 +2,46 @@
-

- <%= t("decidim.elections.votes.confirmed.header") %> -

-

<%= t("decidim.elections.votes.confirmed.lead") %>

-
- <%= t("decidim.elections.votes.confirmed.text", e_vote_poll_id: "qw12Sf35vVb556Hfvh7qw12Sf35vVb556Hfvh7").html_safe %> -

- <%= t("decidim.elections.votes.confirmed.verify_text").html_safe %> -

+
+
+

+ <%= t("decidim.elections.votes.processing.header") %> +

+ +

+ <%= t("decidim.elections.votes.processing.text") %> +

+
- <%= link_to :elections, class: "button" do %> - <%= t("decidim.elections.votes.confirmed.back") %> - <% end %> -
- <%= t("decidim.elections.votes.confirmed.experience") %> - <%= link_to t("decidim.elections.votes.confirmed.feedback"), election_feedback_path(election) %> +
+

+ <%= t("decidim.elections.votes.confirmed.header") %> +

+ +

<%= t("decidim.elections.votes.confirmed.lead") %>

+
+ <%= t("decidim.elections.votes.confirmed.text", e_vote_poll_id: encrypted_vote_hash).html_safe %> +

+ <%= t("decidim.elections.votes.confirmed.verify_text").html_safe %> +

+
+ + <%= link_to :elections, class: "button" do %> + <%= t("decidim.elections.votes.confirmed.back") %> + <% end %> +
+ <%= t("decidim.elections.votes.confirmed.experience") %> + <%= link_to t("decidim.elections.votes.confirmed.feedback"), election_feedback_path(election) %> +
diff --git a/decidim-elections/app/views/decidim/elections/votes/_election_votes_failed.html.erb b/decidim-elections/app/views/decidim/elections/votes/_election_votes_failed.html.erb new file mode 100644 index 0000000000000..cbdb4b0c85ca0 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/votes/_election_votes_failed.html.erb @@ -0,0 +1,21 @@ +<%= render("election_votes_header", header_title: t("decidim.elections.votes.header.confirmed")) %> + +
+
+

+ <%= t("decidim.elections.votes.failed.header") %> +

+

<%= t("decidim.elections.votes.failed.lead") %>

+
+ <%= t("decidim.elections.votes.failed.text") %> +
+ + <%= link_to :elections, class: "button" do %> + <%= t("decidim.elections.votes.confirmed.back") %> + <% end %> +
+ <%= t("decidim.elections.votes.confirmed.experience") %> + <%= link_to t("decidim.elections.votes.confirmed.feedback"), election_feedback_path(election) %> +
+
+
diff --git a/decidim-elections/app/views/decidim/elections/votes/cast_failed.js.erb b/decidim-elections/app/views/decidim/elections/votes/cast_failed.js.erb new file mode 100644 index 0000000000000..22a5898b70647 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/votes/cast_failed.js.erb @@ -0,0 +1 @@ +$('#confirmed_page').html('<%= j(render(partial: "election_votes_failed")) %>') diff --git a/decidim-elections/app/views/decidim/elections/votes/cast_success.js.erb b/decidim-elections/app/views/decidim/elections/votes/cast_success.js.erb new file mode 100644 index 0000000000000..df66749cf2ca1 --- /dev/null +++ b/decidim-elections/app/views/decidim/elections/votes/cast_success.js.erb @@ -0,0 +1 @@ +$('#confirmed_page').html('<%= j(render(partial: "election_votes_confirmed", locals: { encrypted_vote_hash: @form.encrypted_vote_hash })) %>') diff --git a/decidim-elections/app/views/decidim/elections/votes/new.html.erb b/decidim-elections/app/views/decidim/elections/votes/new.html.erb index b9aa308f4f756..616b632c6b1a5 100644 --- a/decidim-elections/app/views/decidim/elections/votes/new.html.erb +++ b/decidim-elections/app/views/decidim/elections/votes/new.html.erb @@ -1,44 +1,51 @@ -<% questions.each_with_index do |step_question, step_index| %> -
" - data-toggler=".hide"> - - <%= render( - "election_votes_steps_header", - step: step_index, - questions_count: questions_count - ) %> - -
-
- <%= render( - "election_votes_question", - question: step_question +
+ <% questions.each_with_index do |step_question, step_index| %> +
" + data-toggler=".hide"> + <%= render( + "election_votes_steps_header", + step: step_index, + questions_count: questions_count ) %> + +
+
+ <%= render( + "election_votes_question", + question: step_question + ) %> +
+ + <%= cell( + "decidim/elections/voting_step_navigation", + step_index, + total_steps: questions_count + ) %> +
+ <% end %> - <%= cell( - "decidim/elections/voting_step_navigation", - step_index, - total_steps: questions_count +
+ <%= render( + "election_votes_confirm", + questions: questions ) %> -
-<% end %> - -
- <%= render( - "election_votes_confirm", - questions: questions - ) %> -
-
- <%= render("election_votes_encrypting") %> -
+
+ <%= render("election_votes_encrypting") %> +
-
- <%= render("election_votes_confirmed") %> +
+
<%= javascript_include_tag("decidim/elections/vote") %> diff --git a/decidim-elections/app/views/decidim/votings/admin/votings/_form.html.erb b/decidim-elections/app/views/decidim/votings/admin/votings/_form.html.erb index 5d569a6e25a98..b2f304d3a05a2 100644 --- a/decidim-elections/app/views/decidim/votings/admin/votings/_form.html.erb +++ b/decidim-elections/app/views/decidim/votings/admin/votings/_form.html.erb @@ -12,15 +12,6 @@ <%= form.translated :text_field, :title, autofocus: true %>
-
-
- <%= form.text_field :slug %> -

- <%== t(".slug_help", url: decidim_form_slug_url(:votings, form.object.slug)) %> -

-
-
-
<%= form.translated :editor, :description, toolbar: :full, lines: 25 %>
@@ -39,6 +30,15 @@ <%= scopes_picker_field form, :scope_id, root: nil %>
+
+
+ <%= form.text_field :slug %> +

+ <%== t(".slug_help", url: decidim_form_slug_url(:votings, form.object.slug)) %> +

+
+
+
<%= form.upload :banner_image %> diff --git a/decidim-elections/app/views/decidim/votings/admin/votings/edit.html.erb b/decidim-elections/app/views/decidim/votings/admin/votings/edit.html.erb new file mode 100644 index 0000000000000..dd8815f41efa2 --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/admin/votings/edit.html.erb @@ -0,0 +1,31 @@ +<%= decidim_form_for @form, + method: :patch, + url: voting_path(@current_voting), + html: { class: "form edit_voting" } do |f| %> + <%= render partial: "form", object: f %> +
+ <%= f.submit t("votings.edit.update", scope: "decidim.votings.admin"), class: "button" %> + + <% if allowed_to? :publish, :voting, voting: @current_voting %> + <% if @current_voting.published? %> + <%= link_to t("votings.actions.unpublish", scope: "decidim.votings.admin"), + url_for(action: :unpublish, id: @current_voting, controller: "votings"), + method: :put, + class: "button muted" %> + <% else %> + <%= link_to t("votings.actions.publish", scope: "decidim.votings.admin"), + url_for(action: :publish, id: @current_voting, controller: "votings"), + method: :put, + class: "button hollow" %> + <% end %> + <% end %> + + <% if allowed_to? :destroy, :voting, voting: @current_voting %> + <%= link_to t("votings.actions.destroy", scope: "decidim.votings.admin"), + @current_voting, + method: :delete, + class: "alert button", + data: { confirm: t("votings.actions.confirm_destroy", scope: "decidim.votings.admin") } %> + <% end %> +
+<% end %> diff --git a/decidim-elections/app/views/decidim/votings/admin/votings/index.html.erb b/decidim-elections/app/views/decidim/votings/admin/votings/index.html.erb index 5160c9c49acbf..3666ebb840bcc 100644 --- a/decidim-elections/app/views/decidim/votings/admin/votings/index.html.erb +++ b/decidim-elections/app/views/decidim/votings/admin/votings/index.html.erb @@ -24,9 +24,18 @@ <% votings.each do |voting| %> - <%= translated_attribute(voting.title) %> + <% if allowed_to? :update, :voting, voting: voting %> + <%= link_to translated_attribute(voting.title), edit_voting_path(voting) %> +
+ <% elsif allowed_to? :preview, :voting, voting: voting %> + <%= link_to translated_attribute(voting.title), + decidim_votings.voting_path(voting), + target: "_blank" %> +
+ <% else %> + <%= translated_attribute(voting.title) %> + <% end %> - <%= l voting.created_at, format: :short %> @@ -43,6 +52,24 @@ <% end %> + <% if allowed_to? :update, :voting, voting: voting %> + <%= icon_link_to "pencil", + edit_voting_path(voting), + t("actions.configure", scope: "decidim.admin"), + class: "action-icon--edit" %> + <% else %> + + <% end %> + + <% if allowed_to? :preview, :voting, voting: voting %> + <%= icon_link_to "eye", + decidim_votings.voting_path(voting), + t("actions.preview", scope: "decidim.admin"), + class: "action-icon--preview", + target: "_blank" %> + <% else %> + + <% end %> <% end %> diff --git a/decidim-elections/app/views/decidim/votings/votings/_count.html.erb b/decidim-elections/app/views/decidim/votings/votings/_count.html.erb new file mode 100644 index 0000000000000..e127675765d93 --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/_count.html.erb @@ -0,0 +1 @@ +<%= t(".title", count: count) %> diff --git a/decidim-elections/app/views/decidim/votings/votings/_filters.html.erb b/decidim-elections/app/views/decidim/votings/votings/_filters.html.erb new file mode 100644 index 0000000000000..6ceb31c799aea --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/_filters.html.erb @@ -0,0 +1,30 @@ +<%= render partial: "decidim/shared/filter_form_help", locals: { skip_to_id: "votings" } %> + +<%= filter_form_for filter do |form| %> +
+ +
+ + <%= form.collection_radio_buttons :state, + options_for_state_filter, + :first, + :last, + { legend_title: t("consultations.filters.state", scope: "decidim") }, + "aria-controls": "consultations" %> + + <%= hidden_field_tag :order, order, id: nil, class: "order_filter" %> +<% end %> diff --git a/decidim-elections/app/views/decidim/votings/votings/_filters_small_view.html.erb b/decidim-elections/app/views/decidim/votings/votings/_filters_small_view.html.erb new file mode 100644 index 0000000000000..5eefcc8896bca --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/_filters_small_view.html.erb @@ -0,0 +1,23 @@ +
+ +
+ +
+
+

<%= t ".filter_by" %>:

+ +
+
+ <%= render partial: "filters" %> +
+
diff --git a/decidim-elections/app/views/decidim/votings/votings/_voting_details.html.erb b/decidim-elections/app/views/decidim/votings/votings/_voting_details.html.erb new file mode 100644 index 0000000000000..096f7c86bb14c --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/_voting_details.html.erb @@ -0,0 +1,7 @@ +
+
+

<%= decidim_sanitize translated_attribute(voting.description), strip_tags: true %>

+
+
+
+
diff --git a/decidim-elections/app/views/decidim/votings/votings/_votings.html.erb b/decidim-elections/app/views/decidim/votings/votings/_votings.html.erb new file mode 100644 index 0000000000000..64b102a954b17 --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/_votings.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= order_selector available_orders, i18n_scope: "decidim.votings.votings.orders" %> +
+ +
+ <%= render partial: "decidim/shared/results_per_page" %> +
+
+ +
+ <% votings.each do |voting| %> + <%= card_for voting %> + <% end %> +
+<%= decidim_paginate votings %> diff --git a/decidim-elections/app/views/decidim/votings/votings/index.html.erb b/decidim-elections/app/views/decidim/votings/votings/index.html.erb new file mode 100644 index 0000000000000..32651b5ac73e3 --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/index.html.erb @@ -0,0 +1,35 @@ +<% +edit_link( + decidim_admin_votings.votings_path, + :read, + :voting +) +%> +<% provide :meta_title, t("votings.index.title", scope: "decidim.votings") %> + +<%= participatory_space_wrapper do %> +
+
+
+

+ <%= render partial: "count", locals: { count: votings.count } %> +

+
+
+
+ +
+
+ <%= render partial: "filters_small_view" %> +
+ <%= render partial: "filters" %> +
+
+
+ <%= render partial: "votings" %> +
+
+<% end %> + +<%= javascript_include_tag "decidim/filters" %> +<%= javascript_include_tag "decidim/results_listing" %> diff --git a/decidim-elections/app/views/decidim/votings/votings/index.js.erb b/decidim-elections/app/views/decidim/votings/votings/index.js.erb new file mode 100644 index 0000000000000..5e4f78e91ca02 --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/index.js.erb @@ -0,0 +1,10 @@ +var $votings = $('#votings'); +var $votingsCount = $('#votings-count'); +var $orderFilterInput = $('.order_filter'); + +$votings.html('<%= j(render partial: "votings") %>'); +$votingsCount.html('<%= j(render partial: "count", locals: { count: votings.count }) %>'); +$orderFilterInput.val('<%= order %>'); + +var $dropdownMenu = $('.dropdown.menu', $votings); +$dropdownMenu.foundation(); diff --git a/decidim-elections/app/views/decidim/votings/votings/show.html.erb b/decidim-elections/app/views/decidim/votings/votings/show.html.erb new file mode 100644 index 0000000000000..bcfc97055c24c --- /dev/null +++ b/decidim-elections/app/views/decidim/votings/votings/show.html.erb @@ -0,0 +1,10 @@ +<% +edit_link( + resource_locator(current_voting).edit, + :update, + :voting, + voting: current_voting +) +%> + +<%= render partial: "voting_details", locals: { voting: current_voting } %> diff --git a/decidim-elections/app/views/layouts/decidim/_voting_header.html.erb b/decidim-elections/app/views/layouts/decidim/_voting_header.html.erb new file mode 100644 index 0000000000000..644287fb4e052 --- /dev/null +++ b/decidim-elections/app/views/layouts/decidim/_voting_header.html.erb @@ -0,0 +1,16 @@ +<%= participatory_space_floating_help %> + +
+
+
+
+
+

+ <%= translated_attribute(current_voting.title) %> +

+
+
+
+
+
diff --git a/decidim-elections/app/views/layouts/decidim/voting.html.erb b/decidim-elections/app/views/layouts/decidim/voting.html.erb new file mode 100644 index 0000000000000..7cb2c2899a35d --- /dev/null +++ b/decidim-elections/app/views/layouts/decidim/voting.html.erb @@ -0,0 +1,16 @@ +<%= render "layouts/decidim/application" do %> +
+ <%= render partial: "layouts/decidim/voting_header" %> + <%= cell "decidim/translation_bar", current_organization %> + <%= yield %> +
+ <% if content_for? :expanded %> +
+
+
+ <%= yield :expanded %> +
+
+
+ <% end %> +<% end %> diff --git a/decidim-elections/config/locales/en.yml b/decidim-elections/config/locales/en.yml index bf07ea26824eb..d341c17682cbb 100644 --- a/decidim-elections/config/locales/en.yml +++ b/decidim-elections/config/locales/en.yml @@ -17,6 +17,9 @@ en: max_selections: Maximum number of selections min_selections: None of the above option title: Title + voting: + end_time: End time + start_time: Start time errors: models: answer: @@ -29,6 +32,8 @@ en: needs_to_be_reattached: Needs to be reattached trustee: attributes: + name: + cant_be_changed: can't be changed public_key: cant_be_changed: can't be changed activerecord: @@ -42,6 +47,9 @@ en: decidim/elections/question: one: Question other: Questions + decidim/votings/voting: + one: Voting + other: Votings decidim: components: elections: @@ -62,10 +70,10 @@ en: import: Import proposals to answers manage_answers: Manage answers manage_questions: Manage questions + manage_steps: Manage steps new: New %{name} preview: Preview publish: Publish - setup: Setup title: Actions unpublish: Unpublish admin: @@ -114,9 +122,6 @@ en: title: New election publish: success: The election has been successfully published. - setup: - invalid: There was a problem setting up this election - success: Election successfully sent to the Bulletin Board unpublish: success: The election has been successfully unpublished. update: @@ -161,29 +166,61 @@ en: update: invalid: There was a problem updating this question success: Question successfully updated - setup: - errors: - max_selections: The questions do not have a correct value for amount of answers - minimum_answers: Questions must have at least one answer. - minimum_questions: The election must have at least one question. - published: The election is not published - time_before: The start time is in less than 3 hours before the election starts - trustee_public_key: does not have a valid public key - trustees_quorum: The amount of trustees does not correspond with the trustees quorum required - requirements: - max_selections: All the questions have a correct value for maximum of answers - minimum_answers: Each question has at least 2 answers - minimum_questions: The election has at least 1 question - published: The election is published - time_before: The setup is being done at least 3 hours before the election starts - trustee_public_key: has a valid public key - trustees_quorum: The size of this list of trustees is correct and it will be needed at least %{quorum} trustees to perform the tally process - show: - setup: Setup election - title: Election setup - update: - setup: Setup election - title: Election setup + steps: + create_election: + errors: + max_selections: The questions do not have a correct value for amount of answers + minimum_answers: Questions must have at least one answer. + minimum_questions: The election must have at least one question. + published: The election is not published. + time_before: The start time is in less than 3 hours before the election starts. + trustees_number: The participatory space must have at least %{number} trustees with public key. + invalid: There was a problem setting up this election + no_trustees: There are no Trustees configured for this participatory space + not_used_trustee: "(not used)" + public_key: + 'false': does not have a public key + 'true': has a public key + requirements: + max_selections: All the questions have a correct value for maximum of answers. + minimum_answers: Each question has at least 2 answers. + minimum_questions: The election has at least 1 question. + published: The election is published. + time_before: The setup is being done at least %{hours} hours before the election starts. + trustees_number: The participatory space has at least %{number} trustees with public key. + submit: Setup election + success: Election successfully sent to the Bulletin Board + title: Setup election + trustees: Election Trustees + index: + title: Manage elections steps on the Bulletin Board + key_ceremony: + title: Key ceremony + trustees: Trustees + ready: + errors: + time_before: The election is ready to start. You have to wait until %{hours} hours before the starting time (%{start_time}) to open the ballot box. + invalid: There was a problem opening the ballot box + requirements: + time_before: The election will start soon. You can open the ballot box manually, or it will be opened automatically before the starting time, at %{start_time}. + submit: Open ballot box + success: The ballot box was successfully opened in the Bulletin Board + title: Ready to start + results: + title: Results calculated + results_published: + title: Results published + tally: + title: Tally process + vote: + errors: + time_after: The election is still ongoing. You have to wait until the ending time (%{end_time}) to close the ballot box. + invalid: There was a problem closing the ballot box + requirements: + time_after: The election has ended. You can close the ballot box manually, or it will be closed automatically in a few minutes. + submit: Close ballot box + success: The ballot box was successfully closed in the Bulletin Board + title: Vote period trustees_participatory_spaces: actions: disable: Disable @@ -207,7 +244,10 @@ en: success: Trustee %{trustee} successfully updated admin_log: election: + close_ballot_box: "%{user_name} closed the ballot box for the %{resource_name} election on the Bulletin Board" + open_ballot_box: "%{user_name} opened the ballot box for the %{resource_name} election on the Bulletin Board" publish: "%{user_name} published the %{resource_name} election" + setup: "%{user_name} created the %{resource_name} election on the Bulletin Board" unpublish: "%{user_name} unpublished the %{resource_name} election" election_m: badge_name: @@ -272,6 +312,7 @@ en: title: Title election: fields: + bb_status: Bulletin Board status end_time: End at start_time: Starts at title: Title @@ -294,14 +335,51 @@ en: older: Older recent: Recent trustee_zone: + elections: + backup_modal: + description: This election is being created in the Bulletin Board. Is is very important that every Trustee participating in it creates a backup copy of these keys and store them in a safe place. After that, the process continues. + download_election_keys: Download keys + download_icon: Icon that indicates a download action + title: Backup election keys for %{election} + key_ceremony_steps: + back: Back + description: This election is being created in the Bulletin Board. To complete this process, your participitation as a Trustee is needed. + keys: + create_election: Keys generation + key_ceremony: + joint_election_key: Joint Key generation + step_1: Keys publishing + list: + status: Status + task: Task + process_warning: Once the process is started, you should not exit this page until the process ends. It will take several minutes, as all Trustees should be connected to complete it. + start: Start + start_icon: Icon that indicates a start button to start the election keys generation + status: + completed: Completed + pending: Pending + processing: Processing + title: Create election keys for %{election} + restore_modal: + description: The Bulletin Board has information from you as a Trustee on this election. To continue the process, first upload the backup file generated during the previous session. + title: Restore election keys for %{election} + upload_election_keys: Upload election keys + upload_icon: Icon that indicates a download action + update: + error: The election status wasn't updated. + success: 'The election status is: %{status}' menu: trustee_zone: Trustee zone trustees: show: elections: list: + action_required: + 'false': 'No' + name: Action required? + 'true': Perform action + bb_status: Status election: Election - status: Status voting_period: Voting period no_elections: There are no elections where you act as a Trustee. title: Elections @@ -350,6 +428,10 @@ en: encrypting: header: Encoding vote... text: Your vote is being encrypted to ensure you can cast it anonymously. + failed: + header: Vote failed + lead: Your vote has not been casted! + text: Something went wrong, please try it again. header: confirm: Confirm your vote confirmed: Vote confirmed @@ -366,6 +448,9 @@ en: preview_alert: This is a preview of the voting booth. question_steps: Question %{current_step} of %{total_steps} selections: "%{selected} of %{max_selections}
selections" + processing: + header: Processing vote... + text: Your vote has been received and it is being processing. Please wait. voting_step: back: Back continue: Next @@ -390,6 +475,8 @@ en: email_outro: You have received this notification because you've been added as trustee for %{resource_name}. email_subject: You are a trustee for %{resource_name}. notification_title: You are a trustee for %{resource_name}. + menu: + votings: Votings pages: home: statistics: @@ -414,21 +501,70 @@ en: votings: Votings votings: actions: + confirm_destroy: Are you sure? + destroy: Destroy new_voting: New Voting Space + publish: Publish + unpublish: Unpublish create: invalid: There was a problem creating this voting success: Voting successfully created + edit: + update: Update form: slug_help: 'URL slugs are used to generate the URLs that point to this voting. Only accepts letters, numbers and dashes, and must start with a letter. Example: %{url}' title: Title new: create: Create title: New Voting + publish: + success: Voting successfully published + unpublish: + success: Voting successfully unpublished + update: + invalid: There was a problem updating this voting + success: Voting successfully updated admin_log: voting: create: "%{user_name} created the %{resource_name} voting" publish: "%{user_name} published the %{resource_name} voting" unpublish: "%{user_name} unpublished the %{resource_name} voting" + content_blocks: + highlighted_votings: + name: Highlighted votings + votings: + count: + title: + one: "%{count} voting" + other: "%{count} votings" + filters: + active: Active + all: All + finished: Finished + search: Search + state: Status + upcoming: Upcoming + filters_small_view: + close_modal: Close modal + filter: Filter + filter_by: Filter by + unfold: Unfold + index: + title: Votings + orders: + label: 'Sort votings by:' + random: Random + recent: Most recent + show: + dates: Dates + votings_m: + badge_name: + finished: Finished + ongoing: Ongoing + upcoming: Upcoming + footer_button_text: + view: View + unspecified: Not specified layouts: decidim: election_votes_header: diff --git a/decidim-elections/db/migrate/20201209110653_create_decidim_elections_votes.rb b/decidim-elections/db/migrate/20201209110653_create_decidim_elections_votes.rb new file mode 100644 index 0000000000000..999e9cb7d51b0 --- /dev/null +++ b/decidim-elections/db/migrate/20201209110653_create_decidim_elections_votes.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateDecidimElectionsVotes < ActiveRecord::Migration[5.2] + def change + create_table :decidim_elections_votes do |t| + t.belongs_to :decidim_elections_election, null: false, index: { name: "index_elections_votes_on_decidim_elections_election_id" } + t.string :voter_id, null: false + t.string :encrypted_vote_hash, null: false + t.string :status, null: false + + t.timestamps + end + end +end diff --git a/decidim-elections/db/migrate/20201216091123_add_name_to_trustees.rb b/decidim-elections/db/migrate/20201216091123_add_name_to_trustees.rb new file mode 100644 index 0000000000000..f17f7eb020b38 --- /dev/null +++ b/decidim-elections/db/migrate/20201216091123_add_name_to_trustees.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddNameToTrustees < ActiveRecord::Migration[5.2] + def change + add_column :decidim_elections_trustees, :name, :string, null: true, unique: true + end +end diff --git a/decidim-elections/decidim-elections.gemspec b/decidim-elections/decidim-elections.gemspec index 1702e101a9d0a..860b00a171d8e 100644 --- a/decidim-elections/decidim-elections.gemspec +++ b/decidim-elections/decidim-elections.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE-AGPLv3.txt", "Rakefile", "README.md"] - s.add_dependency "decidim-bulletin_board", "0.2.0" + s.add_dependency "decidim-bulletin_board", "0.6.1" s.add_dependency "decidim-core", Decidim::Elections.version s.add_dependency "decidim-forms", Decidim::Elections.version s.add_dependency "decidim-proposals", Decidim::Elections.version diff --git a/decidim-elections/lib/decidim/elections.rb b/decidim-elections/lib/decidim/elections.rb index 0384282635947..f6b5889f85c4a 100644 --- a/decidim-elections/lib/decidim/elections.rb +++ b/decidim-elections/lib/decidim/elections.rb @@ -16,8 +16,20 @@ module Decidim module Elections autoload :AnswerSerializer, "decidim/elections/answer_serializer" + include ActiveSupport::Configurable + def self.bulletin_board @bulletin_board ||= Decidim::BulletinBoard::Client.new end + + # Public Setting that defines how many hours should the setup be run before the election starts + config_accessor :setup_minimum_hours_before_start do + 3 + end + + # Public Setting that defines how many hours the ballot box can be opened before the election starts + config_accessor :open_ballot_box_maximum_hours_before_start do + 6 + end end end diff --git a/decidim-elections/lib/decidim/elections/admin_engine.rb b/decidim-elections/lib/decidim/elections/admin_engine.rb index 495f048f5d96b..6855134c285b5 100644 --- a/decidim-elections/lib/decidim/elections/admin_engine.rb +++ b/decidim-elections/lib/decidim/elections/admin_engine.rb @@ -13,6 +13,7 @@ class AdminEngine < ::Rails::Engine get "/answer_options", to: "feedback_forms#answer_options", as: :answer_options_election_feedback, defaults: { format: "json" } resources :elections do + resources :steps, only: [:index, :update] member do put :publish put :unpublish @@ -40,7 +41,6 @@ class AdminEngine < ::Rails::Engine resources :trustees, only: [:index, :new, :edit, :create, :destroy], controller: "trustees_participatory_spaces" - resources :setup, only: [:show, :update] root to: "elections#index" end diff --git a/decidim-elections/lib/decidim/elections/component.rb b/decidim-elections/lib/decidim/elections/component.rb index 9f685497b1b83..37ed4812763b5 100644 --- a/decidim-elections/lib/decidim/elections/component.rb +++ b/decidim-elections/lib/decidim/elections/component.rb @@ -113,7 +113,7 @@ description: Decidim::Faker::Localized.wrapped("

", "

") do Decidim::Faker::Localized.paragraph(sentence_count: 3) end, - max_selections: Faker::Number.between(from: 1, to: 5), + max_selections: Faker::Number.between(from: 1, to: 3), weight: Faker::Number.number(digits: 1), random_answers_order: Faker::Boolean.boolean(true_ratio: 0.5), min_selections: Faker::Number.between(from: 0, to: 1) @@ -121,7 +121,7 @@ visibility: "all" ) - rand(2...5).times do + rand(upcoming_question.max_selections...5).times do answer = Decidim.traceability.create!( Decidim::Elections::Answer, admin_user, diff --git a/decidim-elections/lib/decidim/elections/engine.rb b/decidim-elections/lib/decidim/elections/engine.rb index 8691e69d95b1c..d1c018e4e5914 100644 --- a/decidim-elections/lib/decidim/elections/engine.rb +++ b/decidim-elections/lib/decidim/elections/engine.rb @@ -15,7 +15,9 @@ class Engine < ::Rails::Engine post :answer end - resource :vote + resource :vote, only: [:new] do + post :cast + end end root to: "elections#index" diff --git a/decidim-elections/lib/decidim/elections/test/factories.rb b/decidim-elections/lib/decidim/elections/test/factories.rb index a6ca40a6535dd..5dde4089af218 100644 --- a/decidim-elections/lib/decidim/elections/test/factories.rb +++ b/decidim-elections/lib/decidim/elections/test/factories.rb @@ -4,6 +4,10 @@ require "decidim/forms/test/factories" FactoryBot.define do + sequence(:private_key) do + JWT::JWK.new(OpenSSL::PKey::RSA.new(4096)) + end + factory :elections_component, parent: :component do name { Decidim::Components::Namer.new(participatory_space.organization.available_locales, :elections).i18n_name } manifest_name { :elections } @@ -11,6 +15,10 @@ end factory :election, class: "Decidim::Elections::Election" do + transient do + organization { build(:organization) } + end + upcoming title { generate_localized_title } description { Decidim::Faker::Localized.wrapped("

", "

") { generate_localized_title } } @@ -19,18 +27,19 @@ blocked_at { nil } bb_status { nil } questionnaire - component { create(:elections_component) } + component { create(:elections_component, organization: organization) } + + trait :bb_test do + bb_status { "key_ceremony" } + id { (10_000 + Decidim::Elections::Election.bb_statuses.keys.index(bb_status)) } + end trait :upcoming do start_time { 1.day.from_now } - blocked_at { Time.current } - bb_status { "key_ceremony" } end trait :started do start_time { 2.days.ago } - blocked_at { Time.current } - bb_status { "key_ceremony" } end trait :ongoing do @@ -41,7 +50,6 @@ started end_time { 1.day.ago } blocked_at { Time.current } - bb_status { "key_ceremony" } end trait :published do @@ -58,28 +66,63 @@ end trait :ready_for_setup do + transient do + trustee_keys { 2.times.map { [Faker::Name.name, generate(:private_key).export.to_json] }.to_h } + end + + upcoming + published complete - after(:create) do |election, _evaluator| - create_list(:trustees_participatory_space, 2, :trustee_ready, participatory_space: election.component.participatory_space) + + after(:create) do |election, evaluator| + evaluator.trustee_keys.each do |name, key| + create(:trustee, :with_public_key, name: name, election: election, public_key: key) + end + end + end + + trait :created do + ready_for_setup + blocked_at { start_time - 1.day } + + start_time { 1.hour.from_now } + bb_status { "key_ceremony" } + + after(:create) do |election| + trustees_participatory_spaces = Decidim::Elections::TrusteesParticipatorySpace.where(participatory_space: election.component.participatory_space) + election.trustees << trustees_participatory_spaces.map(&:trustee) end end + trait :ready do + created + bb_status { "ready" } + end + + trait :vote do + created + ongoing + bb_status { "vote" } + end + trait :results do - started - end_time { 1.day.ago } - blocked_at { Time.current } + created + ongoing + finished bb_status { "results" } - after(:build) do |election, _evaluator| - election.questions << build_list(:question, 3, :with_votes, election: election) + + after(:build) do |election| + election.questions.each do |question| + question.answers.each do |answer| + answer.votes_count = Faker::Number.number(digits: 1) + end + end end end trait :results_published do - finished + results bb_status { "results_published" } - after(:build) do |election, _evaluator| - election.questions << build_list(:question, 3, :with_votes, election: election) - end end end @@ -153,38 +196,51 @@ end factory :trustee, class: "Decidim::Elections::Trustee" do + transient do + election { nil } + organization { election&.component&.participatory_space&.organization || create(:organization) } + end + public_key { nil } - user + user { build(:user, organization: organization) } trait :considered do - after(:build) do |trustee, _evaluator| - trustee.trustees_participatory_spaces << build(:trustees_participatory_space) + after(:build) do |trustee, evaluator| + trustee.trustees_participatory_spaces << build(:trustees_participatory_space, trustee: trustee, election: evaluator.election, organization: evaluator.organization) end end trait :with_elections do - after(:build) do |trustee, _evaluator| - trustee.elections << build(:election) + after(:build) do |trustee, evaluator| + trustee.elections << build(:election, :upcoming, organization: evaluator.organization) end end trait :with_public_key do considered - sequence(:public_key) do - private_key = JWT::JWK.new(OpenSSL::PKey::RSA.new(4096)) - public_key = private_key.export - public_key.to_json - end + name { Faker::Name.name } + public_key { generate(:private_key).export.to_json } end end factory :trustees_participatory_space, class: "Decidim::Elections::TrusteesParticipatorySpace" do - participatory_space { create(:participatory_process) } + transient do + organization { election&.component&.participatory_space&.organization || create(:organization) } + election { nil } + end + participatory_space { election&.component&.participatory_space || create(:participatory_process, organization: organization) } considered { true } - trustee + trustee { create(:trustee, organization: organization) } trait :trustee_ready do association :trustee, :with_public_key end end + + factory :vote, class: "Decidim::Elections::Vote" do + election { create(:election) } + sequence(:voter_id) { |n| "voter_#{n}" } + encrypted_vote_hash { "adf89asd0f89das7f" } + status { Decidim::Elections::Vote::PENDING_STATUS } + end end diff --git a/decidim-elections/lib/decidim/elections/trustee_zone_engine.rb b/decidim-elections/lib/decidim/elections/trustee_zone_engine.rb index 27890a68da2ef..b2d19b87b19ba 100644 --- a/decidim-elections/lib/decidim/elections/trustee_zone_engine.rb +++ b/decidim-elections/lib/decidim/elections/trustee_zone_engine.rb @@ -11,7 +11,11 @@ class TrusteeZoneEngine < ::Rails::Engine paths["lib/tasks"] = nil routes do - resource :trustee, path: "/", only: [:show, :update] + resource :trustee, path: "/", only: [:show, :update] do + resources :election, only: [] do + resource :elections, only: [:show, :update] + end + end end def load_seed diff --git a/decidim-elections/lib/decidim/votings/admin_engine.rb b/decidim-elections/lib/decidim/votings/admin_engine.rb index bfcb96c09a8d0..6d27e8e4f9d87 100644 --- a/decidim-elections/lib/decidim/votings/admin_engine.rb +++ b/decidim-elections/lib/decidim/votings/admin_engine.rb @@ -10,7 +10,12 @@ class AdminEngine < ::Rails::Engine paths["lib/tasks"] = nil routes do - resources :votings, param: :slug, except: [:show, :destroy] + resources :votings, param: :slug do + member do + put :publish + put :unpublish + end + end end initializer "decidim_votings.admin_menu" do diff --git a/decidim-elections/lib/decidim/votings/engine.rb b/decidim-elections/lib/decidim/votings/engine.rb index c7b1c1ab11f61..b0feee511bcd3 100644 --- a/decidim-elections/lib/decidim/votings/engine.rb +++ b/decidim-elections/lib/decidim/votings/engine.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "decidim/votings/query_extensions" + module Decidim module Votings # This is the engine that runs on the public interface for Votings of `decidim-elections`. @@ -10,10 +12,50 @@ class Engine < ::Rails::Engine paths["lib/tasks"] = nil routes do - # resource :votings, path: "/", only: [:index, :show, :update] + resources :votings, param: :slug, only: [:index, :show, :update] + end + initializer "decidim_votings.assets" do |app| + app.config.assets.precompile += %w( + decidim_votings_manifest.js + decidim_votings_manifest.css + ) + end + + initializer "decidim.stats" do + Decidim.stats.register :votings_count, priority: StatsRegistry::HIGH_PRIORITY do |organization, _start_at, _end_at| + Decidim::Votings::Voting.where(organization: organization).published.count + end + end + + initializer "decidim_votings.add_cells_view_paths" do + Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Votings::Engine.root}/app/cells") + Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Votings::Engine.root}/app/views") # for partials + end + + initializer "decidim_votings.menu" do + Decidim.menu :menu do |menu| + menu.item I18n.t("menu.votings", scope: "decidim"), + decidim_votings.votings_path, + position: 2.7, + if: Decidim::Votings::Voting.where(organization: current_organization).published.any?, + active: :inclusive + end + end + + initializer "decidim_votings.content_blocks" do + Decidim.content_blocks.register(:homepage, :highlighted_votings) do |content_block| + content_block.cell = "decidim/votings/content_blocks/highlighted_votings" + content_block.public_name_key = "decidim.votings.content_blocks.highlighted_votings.name" + content_block.settings_form_cell = "decidim/votings/content_blocks/highlighted_votings_settings_form" + + content_block.settings do |settings| + settings.attribute :max_results, type: :integer, default: 4 + end + end end - def load_seed + initializer "decidim_votings.query_extensions" do + Decidim::Api::QueryType.include Decidim::Votings::QueryExtensions nil end end diff --git a/decidim-elections/lib/decidim/votings/participatory_space.rb b/decidim-elections/lib/decidim/votings/participatory_space.rb index c9534a5ad24bd..e2c3929cb25e8 100644 --- a/decidim-elections/lib/decidim/votings/participatory_space.rb +++ b/decidim-elections/lib/decidim/votings/participatory_space.rb @@ -28,13 +28,8 @@ context.layout = "layouts/decidim/admin/voting" end - # participatory_space.register_on_destroy_account do |user| - # Decidim::VotingsUserRole.where(user: user).destroy_all - # end - participatory_space.seeds do organization = Decidim::Organization.first - # seeds_root = File.join(__dir__, "..", "..", "..", "db", "seeds") 2.times do |n| params = { diff --git a/decidim-elections/lib/decidim/votings/query_extensions.rb b/decidim-elections/lib/decidim/votings/query_extensions.rb new file mode 100644 index 0000000000000..b3933cb1f5245 --- /dev/null +++ b/decidim-elections/lib/decidim/votings/query_extensions.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Decidim + module Votings + # This module's job is to extend the API with custom fields related to + # decidim-votings. + module QueryExtensions + # Public: Extends a type with `decidim-votings`'s fields. + # + # type - A GraphQL::BaseType to extend. + # + # Returns nothing. + def self.included(type) + type.field :votings, + [Decidim::Votings::VotingType], + null: true, + description: "Lists all votings" do + argument :filter, Decidim::ParticipatoryProcesses::ParticipatoryProcessInputFilter, "This argument let's you filter the results", required: false + argument :order, Decidim::ParticipatoryProcesses::ParticipatoryProcessInputSort, "This argument let's you order the results", required: false + end + + type.field :voting, + Decidim::Votings::VotingType, + null: true, + description: "Finds a voting" do + argument :id, GraphQL::Types::ID, "The ID of the participatory space", required: false + end + end + + def votings(filter: {}, order: {}) + manifest = Decidim.participatory_space_manifests.select { |m| m.name == :votings }.first + + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + end + + def voting(id: nil) + manifest = Decidim.participatory_space_manifests.select { |m| m.name == :votings }.first + + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id }, context) + end + end + end +end diff --git a/decidim-elections/lib/decidim/votings/test/factories.rb b/decidim-elections/lib/decidim/votings/test/factories.rb index 32e6974bc3d6a..75111c2ee309a 100644 --- a/decidim-elections/lib/decidim/votings/test/factories.rb +++ b/decidim-elections/lib/decidim/votings/test/factories.rb @@ -17,6 +17,8 @@ start_time { 1.day.from_now } end_time { 3.days.from_now } decidim_scope_id { create(:scope, organization: organization).id } + banner_image { Decidim::Dev.test_file("city2.jpeg", "image/jpeg") } + introductory_image { Decidim::Dev.test_file("city.jpeg", "image/jpeg") } trait :unpublished do published_at { nil } @@ -25,5 +27,20 @@ trait :published do published_at { Time.current } end + + trait :upcoming do + start_time { 7.days.from_now } + end_time { 1.month.from_now + 7.days } + end + + trait :ongoing do + start_time { 7.days.ago } + end_time { 1.month.from_now - 7.days } + end + + trait :finished do + start_time { 1.month.ago - 7.days } + end_time { 7.days.ago } + end end end diff --git a/decidim-elections/lib/tasks/decidim_elections.rake b/decidim-elections/lib/tasks/decidim_elections.rake index 63889f6ae7b10..665d6acd7a9e5 100644 --- a/decidim-elections/lib/tasks/decidim_elections.rake +++ b/decidim-elections/lib/tasks/decidim_elections.rake @@ -1,10 +1,9 @@ # frozen_string_literal: true namespace :decidim_elections do - desc "Add a new client to the bulletin board" - IDENTIFICATION_PRIVATE_KEY_SIZE = 4096 + desc "Add a new client to the bulletin board" task :generate_identification_keys do identification_jwk_keypair = JWT::JWK.new(OpenSSL::PKey::RSA.new(IDENTIFICATION_PRIVATE_KEY_SIZE)) @@ -17,4 +16,37 @@ namespace :decidim_elections do puts identification_jwk_keypair.export.map { |k, v| "#{k}=#{v}" }.join("&") puts "\nAbove are the generated private and public keys.\n\nSee Decidim docs at docs/services/bulletin_board.md in order to set them up.\n\n" end + + desc "Scheduled tasks" + task :scheduled_tasks do + Decidim::Elections::ElectionsReadyToOpen.for.each do |election| + puts "\nOpening Election ##{election.id}:" + form = Decidim::Elections::Admin::BallotBoxForm.new.with_context(election: election, current_user: nil) + Decidim::Elections::Admin::OpenBallotBox.call(form) do + on(:ok) do + puts "\n✓ Ballot Box opened. New bulletin board status: #{election.bb_status}\n" + end + + on(:invalid) do |message| + puts "\n✗ Ballot Box not opened. Message: #{message}\n" + end + end + puts "\n" + end + + Decidim::Elections::ElectionsFinishedToClose.for.each do |election| + puts "\nClosing Election ##{election.id}:" + form = Decidim::Elections::Admin::BallotBoxForm.new.with_context(election: election, current_user: nil) + Decidim::Elections::Admin::CloseBallotBox.call(form) do + on(:ok) do + puts "\n✓ Ballot Box closed. New bulletin board status: #{election.bb_status}\n" + end + + on(:invalid) do |message| + puts "\n✗ Ballot Box not closed. Message: #{message}\n" + end + end + puts "\n" + end + end end diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/options_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/options_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515.yml new file mode 100644 index 0000000000000..b876c951a9065 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/options_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515.yml @@ -0,0 +1,15 @@ +--- +:scope: trustee 1 download +:url: http://bulletin-board.lvh.me/api +:body: '' +:status: 200 +:method: options +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + Access-Control-Allow-Headers: content-type + Connection: close + Transfer-Encoding: chunked +:content: '' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_3f0f0056aa5aeda91acf5766de3b597d95013d4e.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_3f0f0056aa5aeda91acf5766de3b597d95013d4e.yml new file mode 100644 index 0000000000000..31345aac8bfbc --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_3f0f0056aa5aeda91acf5766de3b597d95013d4e.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 2 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"ProcessKeyCeremonyStep","variables":{"signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzYsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTIiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0yXCJ9In0.gyLqI2VyVNPYqpKgxDIh7maAW0hBwIzRg1yer2kzXuTuixN9SXpFdPzM5KJy9ZLudxHtVQzLDv67tWqMNtSYveYNRkgsZN9WGSm9X4sv0lhAbkASdpPlubhZN_zklyHg7_DYy2od6QvGPasmx1OoCMDGRodpZ-fWAmYW8u-p5Kyuzwjt8Ix20_yi_xuK8kkqBnW_qoyMDQyOTzOJSeAqpaU6ZcgOQwSn2AcbTVkrT9PTYLFV8nGKdV4AcmBYWWi89421GclQzAQ6SYNatTL6WjAdF6TYlq-LzaBsBxji4Q1p7vnn6kamCcUOmgYilDwi7mshLBqJ-iE8prsIifXniwbAirokoGLDhQzxPsC4un8sckW75o3MXqo1vL-hJ7qidlWivoFnzMo-OfxByTPT97y61xgIwDXA5RZ_LbBlBWn5FUygQkAgd0NdCw1IcESVoeAIFIsjiPBvF0-jZO7jydcJdGazyhAYQdhd3yKRDMm3o-_7EiMn4YartwglwhD8wwGK2QOOjbL0wyk0PJZZqhU2WxXvf13SJCWTlaOCue0-Pgz9JbZtMN0RoueFCivsNdvGwqBNIzbtOnZ497k0VjUXgtGtxdoBEnnhnImF8z8L0Oe-VvFsuSTM48YR2MGjil_Ezd1U-InIG9YvO8PIi-aVa3iLz5QmlvirgtyfTvw","messageId":"decidim-test-authority.42.key_ceremony.step_1+t.trustee-2"},"query":"mutation + ProcessKeyCeremonyStep($messageId: String!, $signedData: String!) {\n processKeyCeremonyStep(messageId: + $messageId, signedData: $signedData) {\n pendingMessage {\n signedData\n __typename\n }\n error\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"f495f34b0b8abf4dcbe80f55f78b911c" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 262e564a-4aad-49b9-bfd9-ef097fa450a1 + X-Runtime: '0.030774' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"processKeyCeremonyStep":{"pendingMessage":{"signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzYsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTIiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0yXCJ9In0.gyLqI2VyVNPYqpKgxDIh7maAW0hBwIzRg1yer2kzXuTuixN9SXpFdPzM5KJy9ZLudxHtVQzLDv67tWqMNtSYveYNRkgsZN9WGSm9X4sv0lhAbkASdpPlubhZN_zklyHg7_DYy2od6QvGPasmx1OoCMDGRodpZ-fWAmYW8u-p5Kyuzwjt8Ix20_yi_xuK8kkqBnW_qoyMDQyOTzOJSeAqpaU6ZcgOQwSn2AcbTVkrT9PTYLFV8nGKdV4AcmBYWWi89421GclQzAQ6SYNatTL6WjAdF6TYlq-LzaBsBxji4Q1p7vnn6kamCcUOmgYilDwi7mshLBqJ-iE8prsIifXniwbAirokoGLDhQzxPsC4un8sckW75o3MXqo1vL-hJ7qidlWivoFnzMo-OfxByTPT97y61xgIwDXA5RZ_LbBlBWn5FUygQkAgd0NdCw1IcESVoeAIFIsjiPBvF0-jZO7jydcJdGazyhAYQdhd3yKRDMm3o-_7EiMn4YartwglwhD8wwGK2QOOjbL0wyk0PJZZqhU2WxXvf13SJCWTlaOCue0-Pgz9JbZtMN0RoueFCivsNdvGwqBNIzbtOnZ497k0VjUXgtGtxdoBEnnhnImF8z8L0Oe-VvFsuSTM48YR2MGjil_Ezd1U-InIG9YvO8PIi-aVa3iLz5QmlvirgtyfTvw","__typename":"PendingMessage"},"error":null,"__typename":"ProcessKeyCeremonyStepMutationPayload"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml new file mode 100644 index 0000000000000..c4316035ca673 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 2 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":null},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"3b7f83f1b499c55f03cf4ff9c458b5cc" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 1e4fdccb-75ff-4751-bf23-d8dd30216041 + X-Runtime: '0.024771' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[{"id":"7","messageId":"decidim-test-authority.42.create_election+a.decidim-test-authority","signedData":"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MTA1NjA4MjQsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJUcnVzdGVlIDEiLCJwdWJsaWNfa2V5Ijoie1wiYWxnXCI6XCJSUzI1NlwiLFwiZVwiOlwiQVFBQlwiLFwia3R5XCI6XCJSU0FcIixcIm5cIjpcInJIcE5WT3ZwTHJkTU1BV3NKaDQyVUtubjVzQVVSaEI3QS1zRVFlMGhROGdrNWIzdFZ1ZmFiQUpJc2ZFQXJzanl0OTFVSVhnbVNueUdkOHRia0VyczUzRlljTFhnWndERUoxTk5jQmRIaV9pR0pVTEYxSU9LaHhXSEpTMVFLamtQb1NhbUNUZW9ROGhXcTBtZFdIRjFnc2lPdFJqSWZ0LVJYN0FtMlpxazUzQ2VQRTJUVlJxbkh1RW1NUUhRLWxjUDJQYmUxN2NUNGk2UG95REl1YW5iYWJlbkRrdE56R1hHN0JmRDRPN3poTVdCMGRFMVNRWlNSMDlkYW5Dem81M3hrNE5sb1Y5eENSOVZjU25HdXMtQW5LSVpRWkZYaTBRcUlLNmk4VkZOdUE5UFlDQ2ZsZVVXQ0F5SFJRTWtkOVhTemJUczhPZVdBdmc0UmNiOFd2NUdrTDhhY2F6eTdHMGRpaUF2b3RJcjA1OENOb0ZHcXZ0dUtueGlsUDJhcGFZd0NCVGEtU1FwNERxQ2lZY0F0UGpfV2VSVGJhcDZXeURjZnl4TDVMeVVReXNWbUduNGZCNG5MVm9EV3g0elZtVncwU2JHUVQ4SzRGbWtJaGp0ZERtRUxQYWllbnRHOFRlcDRJQmpQdEZTYS1STUxBdTQ1akhkbHNYQlpqbXJiZVRid1RCV1Y0YVNLcDZUMmxjRU9jLW14cm9ycldnVEdZeTJNdnBKSExhbmVleGVmRU1WQjhpWEpKWDJMNEpBcUtDd3E3VjhDd3VtMFpzVFdhS0xzR1JOcVBlQXdheUFKZVh1aVQ1aERzM2ktY3V5dXFyYzJnbVd4dTVGRUdaSEpRV0dfTUxuWUFUWV9ubElEcS1vdnVHR0phLWxzenU2cHlWdkNLcnlDNzBta3NNXCJ9In0seyJuYW1lIjoiVHJ1c3RlZSAyIiwicHVibGljX2tleSI6IntcImFsZ1wiOlwiUlMyNTZcIixcImVcIjpcIkFRQUJcIixcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCJtWVc0cHk1NWVIVnpsQU9MZVdTbGtYLVV6RmNXbVBaTmd0MzhZZ2ZEc3JXc01pRjk5TnhlUzFoSEJCQkFJTVlWajREZHJhdW54d3hpYkZaSm00QjdKZkxlZE8wMnBHSFhXcHgtVlVTd1JVN1RLQk84S0djTEl1eUlzTEFuNFZabWNVQ2ZPRDR6NEQzbU9QWEpGcm15X2RiNEVRSmdRbmtSUWRvcDlOYi16RXhqWWR3OUlPblNGWWRZTEo2ay1nNE9udTQza1VJVkRqQkJ3QmxOSElrci16cEFwUFdoQUJVcFNWX1Z5NldBZGt1M2l4NE15RlFMUTQ4bGpKcDRLVktydzg1a20zeDVrMXA3eVhwUlhhZUFVd2RsMXVjWlVIZ2ViMjNFRC1xMmd3QVNBYnJCTE9NbHJyUnJleE9iUlhsSGQ3Z2tRUzRmd3ZMdGVoRU5ZMEVlcGFTNlI5WkRHRDVsOEFKT2xTM2pHQ0JmUWRwTDlhSlQwNVpxbUI4VmkwRmo4UGdEa3pzZWxxcDllU3dVQmI4MmJwOHBhZFg5TnNaUGFva1hMdEpsczhhY1NPUHBCRHdtRVBBaVVNTDdMV1V3aVJ5bGltSzRDVkROeHVsTkduQnVQbFlsNWI0dlpRSHRwakJFRmZLVVp4SThxWTFZMnFwRWVoYVlEMG9YYTY0eXIySGk1SVZkbENPUzg4aHFQc2tQdHRvZTI4Y202WVZZZmxKUDlidjRzbHFldTFjYWZEU0N4WDdBNFBYODN3RHBXNWd5V0MzY1hBUFRYaDJQbHhsbm4zcDlLVGFlNl9ZUGtJOElkMjZ6a202MUxwS2ktQ2tRQ3ZvMmZIdmZaSTIzSmlTajNZOUtZWlIyNVltOE85YWFOQmRoZ3ZOS2hCOFZGbGl6S2V0M1ZVOFwifSJ9XSwiZGVzY3JpcHRpb24iOnsibmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkxhYm9yaW9zYW0gdmVsaXQgdm9sdXB0YXRpYnVzLiA1OCIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMS0wMS0xNCAxODowMDoyMCBVVEMiLCJlbmRfZGF0ZSI6IjIwMjEtMDEtMTYgMTg6MDA6MjAgVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSGFydW0gY3VwaWRpdGF0ZSB0ZW1wb3JlLiAxNDUiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJWb2x1cHRhdGUgbmVtbyBldW0uIDE2MyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkRvbG9yZXMgdm9sdXB0YXR1bSBsYWJvcnVtLiAyMTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJSZXByZWhlbmRlcml0IGRlbGVjdHVzIGV0LiAyMTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTZXF1aSBwYXJpYXR1ciBpbGxvLiAxNTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlhIHN1bnQgdXQuIDIwMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFtZXQgYXJjaGl0ZWN0byBhc3N1bWVuZGEuIDIyNiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNhZXBlIHZvbHVwdGF0aWJ1cyBuYW0uIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b3MgY3VtIHNpdC4gMTI3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTmFtIGl0YXF1ZSBlbmltLiAxNzgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjE2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJbmNpZHVudCBhY2N1c2FtdXMgZG9sb3JlbXF1ZS4gMTM5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxOSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVXQgdXQgdml0YWUuIDE0OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2lsaXMgdXQgcXVpLiAxMzYiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIzIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJDb25zZWN0ZXR1ciBxdWlhIGVhcnVtLiAxNjAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaXQgYXV0IHF1aXMuIDE5NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFzcGVyaW9yZXMgZnVnYSBldC4gMjIzIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIzOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTm9uIHF1YWUgb21uaXMuIDIzNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBsYWJvcmlvc2FtIGRlbGVjdHVzLiAxNTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXRlbSBwb3JybyBzaXQuIDE5MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNpbnQgZGlzdGluY3RpbyBtYWduYW0uIDEyNCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlJlcnVtIGV4cGVkaXRhIGRvbG9yZS4gMTcyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpcyBlbmltIGludmVudG9yZS4gMTg0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiT21uaXMgbWFnbmFtIHZvbHVwdGF0ZW0uIDE0MiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b2Qgdml0YWUgdXQuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkJsYW5kaXRpaXMgZGljdGEgZXQuIDIyMCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Iklwc2EgZG9sb3JlcyBpbnZlbnRvcmUuIDIzMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1YXNpIHZvbHVwdGF0ZW0gZmFjZXJlLiAxMjEiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzczIiwic2VxdWVuY2Vfb3JkZXIiOjEsInZvdGVfdmFyaWF0aW9uIjoib25lX29mX20iLCJuYW1lIjoiUGFyaWF0dXIgZWFxdWUgdXQuIDExNSIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJQYXJpYXR1ciBlYXF1ZSB1dC4gMTE1IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPlN1c2NpcGl0IHF1byBlb3MuIDExODwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMTQiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNTIxNCJ9LHsib2JqZWN0X2lkIjoiNTIxMyIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjEzIn0seyJvYmplY3RfaWQiOiI1MjEyIiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6IjUyMTIifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc1Iiwic2VxdWVuY2Vfb3JkZXIiOjIsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkF1dCBhdXRlbSBhY2N1c2FtdXMuIDE2OTwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMzAiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzMCJ9LHsib2JqZWN0X2lkIjoiNTIyNiIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI1MjI2In0seyJvYmplY3RfaWQiOiI1MjI5Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMjkifSx7Im9iamVjdF9pZCI6IjUyMjgiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNTIyOCJ9LHsib2JqZWN0X2lkIjoiNTIyNSIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjI1In0seyJvYmplY3RfaWQiOiI1MjI3Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjUyMjcifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc0Iiwic2VxdWVuY2Vfb3JkZXIiOjMsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJudW1iZXJfZWxlY3RlZCI6MTAsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RXQgdXQgdGVuZXR1ci4gMTMzPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjE4In0seyJvYmplY3RfaWQiOiI1MjI0Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6IjUyMjQifSx7Im9iamVjdF9pZCI6IjUyMjEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIyMSJ9LHsib2JqZWN0X2lkIjoiNTIxNiIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI1MjE2In0seyJvYmplY3RfaWQiOiI1MjE5Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjUyMTkifSx7Im9iamVjdF9pZCI6IjUyMTUiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIxNSJ9LHsib2JqZWN0X2lkIjoiNTIyMyIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI1MjIzIn0seyJvYmplY3RfaWQiOiI1MjIyIiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjUyMjIifSx7Im9iamVjdF9pZCI6IjUyMTciLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiNTIxNyJ9LHsib2JqZWN0X2lkIjoiNTIyMCIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjIwIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6Ijc3NiIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJWb2x1cHRhdGVzIGNvcnJ1cHRpIG5vYmlzLiAyMDgiLCJudW1iZXJfZWxlY3RlZCI6OCwidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiVm9sdXB0YXRlcyBjb3JydXB0aSBub2Jpcy4gMjA4IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkFzcGVybmF0dXIgaXBzYW0gcXVpcy4gMjExPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIzMiIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjMyIn0seyJvYmplY3RfaWQiOiI1MjMxIiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjUyMzEifSx7Im9iamVjdF9pZCI6IjUyMzUiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzNSJ9LHsib2JqZWN0X2lkIjoiNTIzNiIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI1MjM2In0seyJvYmplY3RfaWQiOiI1MjM0Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMzQifSx7Im9iamVjdF9pZCI6IjUyMzgiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIzOCJ9LHsib2JqZWN0X2lkIjoiNTIzMyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjMzIn0seyJvYmplY3RfaWQiOiI1MjM3Iiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjUyMzcifV19XX0sIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmNyZWF0ZV9lbGVjdGlvbithLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.M7os6kWU5Ijp5zBWRE94BvwQ2_iS_fSq_nSKXQxA5G-GGt4X8Qwd2JjFcOhJ_jnIeIJE4-IGBEE3BSNeCXQ73VY2WokPKrMrhBiawepuDoNfjHmj_X5r48aGt6vy1A-u5-bJh0wQQvG6mSF9Xx2uHW8lY4aSgAEEIw9FCTLaqK4k38U0491A8f3u8Es4k4agY7ViBMdFCHWtsBt4bkAiUJXYe8OWucBXFSQA1C4JwsixAUPJ7LVpuAiYshIF-UPBrM-pe56bfslFTRIDY4pGRKQpJimZQfkP2kOoCRw6x4rgS8MXQ1L70pMqRq-pW39EHpcDN-nfgxNJwHxIk4GXUAeDTQT5cG5SjSVXUqiN5dhPWJZOofXQ4FyF1QEgGaBq_J3YJmT6jvNaj_8oUBEnJ8CHa3Yd963l2U7PdDdG6mRuhKRCWI3TbKfc3WoSmHosSTSD5ldexeH4DGs_-N-lO8UPQC5LFcbI0gxUnRa0oiWqkNIdqZx2pFwAsMtI5-afl7EsbnjFQlj6nruBKYcVGhraph5f4fAznHeJgutju7iUXPI2gmyemNcneXP8daNPxn6O0XEe52CDlTZl8V_rzmaizQ3BJNO3DsspRvS55lSsH_ce_3ElGMiFmf5cgt00RmUiTXjLN59gvIsZgQhfD7YxWXUdhkO1stSxCXuuT60","__typename":"LogEntry"},{"id":"8","messageId":"decidim-test-authority.42.key_ceremony.step_1+t.trustee-1","signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzIsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTEiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0xXCJ9In0.aAFO-ql5DzCBeSUrHp-fYWuAzYFDmbaGFfdcP09_yPoWdE-BwsCO9XB43jyjLdDINX-BhkaXClh1NZVtoF5ZjtQINYFu6xG53cd00IUqfKAr9dEMiIqI2msrzo1FvHxnYs_r5b8zKFCWpFTIi6iyROAeOQP0Tkjge8zJRXdEW185SxdwxG5iDLxZWNy-_3fkVIJWKxoS8rB7gdGe-9oDaVJNYcTCz-s-vhfDwjwuIQm1xuT4POkFunlilrnhP_NZ3uQtVMRzOgvVsOXXyl5y0WgFO9J48xCgryIuzZ3puPFRyRuMjJtSdC3BW_g8eOimnN8pTxXDZxIV1SU0HUAG0__6jIzw65dZf32X7g1wuMZEUMCegfv2oQfCNt51aE8aV7P1gszXK1dXtOm4mdxE2O6Cs-KBnANYCl_jWGGyvI99Xul8lnj9sIB0bLR6tAcwOy065xtu3L7Kh_kYpD2g9Ze2YbOWvnBOPB4ry4ew46rHmFYC0j1tUnDtF4j04KRBHbYapjrxQ-Omc3syRZd1jA8ibCHoMcp5LPfS3PZ_-2V__qNu_5sqeLIpM2puhx2iN1nyKiaedDNgohKVVBNqgjHHhymwsmbjGF0coBXddBrVul1NHCmuJ2VjsbQAe3dys6amGIABewtqx9_aySBQ2Vr8VJP7Et9XTIKARUfz0sc","__typename":"LogEntry"}],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_f8ccb88c9a64d84ef571772b2f4d2e310864b243.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_f8ccb88c9a64d84ef571772b2f4d2e310864b243.yml new file mode 100644 index 0000000000000..e7a69c1ad0831 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_0a09842115bf622a155f24c66bf3cc121a8356d5_f8ccb88c9a64d84ef571772b2f4d2e310864b243.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 2 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":"8"},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"14668fa8732050881aef2ce979bf1528" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 620f88a4-cce5-44dd-be0a-21ecbc0e705e + X-Runtime: '0.025421' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml new file mode 100644 index 0000000000000..0b22b7f1d4835 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml @@ -0,0 +1,28 @@ +--- +:scope: complete ceremony with trustee 1 +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":null},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"b8762e301d9041ecff1428101f8b83e4" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 3d5276d5-9ddd-41f9-9180-e2821e5b354f + X-Runtime: '0.024011' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[{"id":"7","messageId":"decidim-test-authority.42.create_election+a.decidim-test-authority","signedData":"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MTA1NjA4MjQsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJUcnVzdGVlIDEiLCJwdWJsaWNfa2V5Ijoie1wiYWxnXCI6XCJSUzI1NlwiLFwiZVwiOlwiQVFBQlwiLFwia3R5XCI6XCJSU0FcIixcIm5cIjpcInJIcE5WT3ZwTHJkTU1BV3NKaDQyVUtubjVzQVVSaEI3QS1zRVFlMGhROGdrNWIzdFZ1ZmFiQUpJc2ZFQXJzanl0OTFVSVhnbVNueUdkOHRia0VyczUzRlljTFhnWndERUoxTk5jQmRIaV9pR0pVTEYxSU9LaHhXSEpTMVFLamtQb1NhbUNUZW9ROGhXcTBtZFdIRjFnc2lPdFJqSWZ0LVJYN0FtMlpxazUzQ2VQRTJUVlJxbkh1RW1NUUhRLWxjUDJQYmUxN2NUNGk2UG95REl1YW5iYWJlbkRrdE56R1hHN0JmRDRPN3poTVdCMGRFMVNRWlNSMDlkYW5Dem81M3hrNE5sb1Y5eENSOVZjU25HdXMtQW5LSVpRWkZYaTBRcUlLNmk4VkZOdUE5UFlDQ2ZsZVVXQ0F5SFJRTWtkOVhTemJUczhPZVdBdmc0UmNiOFd2NUdrTDhhY2F6eTdHMGRpaUF2b3RJcjA1OENOb0ZHcXZ0dUtueGlsUDJhcGFZd0NCVGEtU1FwNERxQ2lZY0F0UGpfV2VSVGJhcDZXeURjZnl4TDVMeVVReXNWbUduNGZCNG5MVm9EV3g0elZtVncwU2JHUVQ4SzRGbWtJaGp0ZERtRUxQYWllbnRHOFRlcDRJQmpQdEZTYS1STUxBdTQ1akhkbHNYQlpqbXJiZVRid1RCV1Y0YVNLcDZUMmxjRU9jLW14cm9ycldnVEdZeTJNdnBKSExhbmVleGVmRU1WQjhpWEpKWDJMNEpBcUtDd3E3VjhDd3VtMFpzVFdhS0xzR1JOcVBlQXdheUFKZVh1aVQ1aERzM2ktY3V5dXFyYzJnbVd4dTVGRUdaSEpRV0dfTUxuWUFUWV9ubElEcS1vdnVHR0phLWxzenU2cHlWdkNLcnlDNzBta3NNXCJ9In0seyJuYW1lIjoiVHJ1c3RlZSAyIiwicHVibGljX2tleSI6IntcImFsZ1wiOlwiUlMyNTZcIixcImVcIjpcIkFRQUJcIixcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCJtWVc0cHk1NWVIVnpsQU9MZVdTbGtYLVV6RmNXbVBaTmd0MzhZZ2ZEc3JXc01pRjk5TnhlUzFoSEJCQkFJTVlWajREZHJhdW54d3hpYkZaSm00QjdKZkxlZE8wMnBHSFhXcHgtVlVTd1JVN1RLQk84S0djTEl1eUlzTEFuNFZabWNVQ2ZPRDR6NEQzbU9QWEpGcm15X2RiNEVRSmdRbmtSUWRvcDlOYi16RXhqWWR3OUlPblNGWWRZTEo2ay1nNE9udTQza1VJVkRqQkJ3QmxOSElrci16cEFwUFdoQUJVcFNWX1Z5NldBZGt1M2l4NE15RlFMUTQ4bGpKcDRLVktydzg1a20zeDVrMXA3eVhwUlhhZUFVd2RsMXVjWlVIZ2ViMjNFRC1xMmd3QVNBYnJCTE9NbHJyUnJleE9iUlhsSGQ3Z2tRUzRmd3ZMdGVoRU5ZMEVlcGFTNlI5WkRHRDVsOEFKT2xTM2pHQ0JmUWRwTDlhSlQwNVpxbUI4VmkwRmo4UGdEa3pzZWxxcDllU3dVQmI4MmJwOHBhZFg5TnNaUGFva1hMdEpsczhhY1NPUHBCRHdtRVBBaVVNTDdMV1V3aVJ5bGltSzRDVkROeHVsTkduQnVQbFlsNWI0dlpRSHRwakJFRmZLVVp4SThxWTFZMnFwRWVoYVlEMG9YYTY0eXIySGk1SVZkbENPUzg4aHFQc2tQdHRvZTI4Y202WVZZZmxKUDlidjRzbHFldTFjYWZEU0N4WDdBNFBYODN3RHBXNWd5V0MzY1hBUFRYaDJQbHhsbm4zcDlLVGFlNl9ZUGtJOElkMjZ6a202MUxwS2ktQ2tRQ3ZvMmZIdmZaSTIzSmlTajNZOUtZWlIyNVltOE85YWFOQmRoZ3ZOS2hCOFZGbGl6S2V0M1ZVOFwifSJ9XSwiZGVzY3JpcHRpb24iOnsibmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkxhYm9yaW9zYW0gdmVsaXQgdm9sdXB0YXRpYnVzLiA1OCIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMS0wMS0xNCAxODowMDoyMCBVVEMiLCJlbmRfZGF0ZSI6IjIwMjEtMDEtMTYgMTg6MDA6MjAgVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSGFydW0gY3VwaWRpdGF0ZSB0ZW1wb3JlLiAxNDUiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJWb2x1cHRhdGUgbmVtbyBldW0uIDE2MyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkRvbG9yZXMgdm9sdXB0YXR1bSBsYWJvcnVtLiAyMTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJSZXByZWhlbmRlcml0IGRlbGVjdHVzIGV0LiAyMTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTZXF1aSBwYXJpYXR1ciBpbGxvLiAxNTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlhIHN1bnQgdXQuIDIwMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFtZXQgYXJjaGl0ZWN0byBhc3N1bWVuZGEuIDIyNiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNhZXBlIHZvbHVwdGF0aWJ1cyBuYW0uIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b3MgY3VtIHNpdC4gMTI3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTmFtIGl0YXF1ZSBlbmltLiAxNzgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjE2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJbmNpZHVudCBhY2N1c2FtdXMgZG9sb3JlbXF1ZS4gMTM5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxOSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVXQgdXQgdml0YWUuIDE0OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2lsaXMgdXQgcXVpLiAxMzYiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIzIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJDb25zZWN0ZXR1ciBxdWlhIGVhcnVtLiAxNjAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaXQgYXV0IHF1aXMuIDE5NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFzcGVyaW9yZXMgZnVnYSBldC4gMjIzIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIzOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTm9uIHF1YWUgb21uaXMuIDIzNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBsYWJvcmlvc2FtIGRlbGVjdHVzLiAxNTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXRlbSBwb3JybyBzaXQuIDE5MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNpbnQgZGlzdGluY3RpbyBtYWduYW0uIDEyNCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlJlcnVtIGV4cGVkaXRhIGRvbG9yZS4gMTcyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpcyBlbmltIGludmVudG9yZS4gMTg0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiT21uaXMgbWFnbmFtIHZvbHVwdGF0ZW0uIDE0MiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b2Qgdml0YWUgdXQuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkJsYW5kaXRpaXMgZGljdGEgZXQuIDIyMCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Iklwc2EgZG9sb3JlcyBpbnZlbnRvcmUuIDIzMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1YXNpIHZvbHVwdGF0ZW0gZmFjZXJlLiAxMjEiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzczIiwic2VxdWVuY2Vfb3JkZXIiOjEsInZvdGVfdmFyaWF0aW9uIjoib25lX29mX20iLCJuYW1lIjoiUGFyaWF0dXIgZWFxdWUgdXQuIDExNSIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJQYXJpYXR1ciBlYXF1ZSB1dC4gMTE1IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPlN1c2NpcGl0IHF1byBlb3MuIDExODwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMTQiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNTIxNCJ9LHsib2JqZWN0X2lkIjoiNTIxMyIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjEzIn0seyJvYmplY3RfaWQiOiI1MjEyIiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6IjUyMTIifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc1Iiwic2VxdWVuY2Vfb3JkZXIiOjIsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkF1dCBhdXRlbSBhY2N1c2FtdXMuIDE2OTwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMzAiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzMCJ9LHsib2JqZWN0X2lkIjoiNTIyNiIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI1MjI2In0seyJvYmplY3RfaWQiOiI1MjI5Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMjkifSx7Im9iamVjdF9pZCI6IjUyMjgiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNTIyOCJ9LHsib2JqZWN0X2lkIjoiNTIyNSIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjI1In0seyJvYmplY3RfaWQiOiI1MjI3Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjUyMjcifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc0Iiwic2VxdWVuY2Vfb3JkZXIiOjMsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJudW1iZXJfZWxlY3RlZCI6MTAsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RXQgdXQgdGVuZXR1ci4gMTMzPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjE4In0seyJvYmplY3RfaWQiOiI1MjI0Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6IjUyMjQifSx7Im9iamVjdF9pZCI6IjUyMjEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIyMSJ9LHsib2JqZWN0X2lkIjoiNTIxNiIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI1MjE2In0seyJvYmplY3RfaWQiOiI1MjE5Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjUyMTkifSx7Im9iamVjdF9pZCI6IjUyMTUiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIxNSJ9LHsib2JqZWN0X2lkIjoiNTIyMyIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI1MjIzIn0seyJvYmplY3RfaWQiOiI1MjIyIiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjUyMjIifSx7Im9iamVjdF9pZCI6IjUyMTciLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiNTIxNyJ9LHsib2JqZWN0X2lkIjoiNTIyMCIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjIwIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6Ijc3NiIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJWb2x1cHRhdGVzIGNvcnJ1cHRpIG5vYmlzLiAyMDgiLCJudW1iZXJfZWxlY3RlZCI6OCwidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiVm9sdXB0YXRlcyBjb3JydXB0aSBub2Jpcy4gMjA4IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkFzcGVybmF0dXIgaXBzYW0gcXVpcy4gMjExPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIzMiIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjMyIn0seyJvYmplY3RfaWQiOiI1MjMxIiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjUyMzEifSx7Im9iamVjdF9pZCI6IjUyMzUiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzNSJ9LHsib2JqZWN0X2lkIjoiNTIzNiIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI1MjM2In0seyJvYmplY3RfaWQiOiI1MjM0Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMzQifSx7Im9iamVjdF9pZCI6IjUyMzgiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIzOCJ9LHsib2JqZWN0X2lkIjoiNTIzMyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjMzIn0seyJvYmplY3RfaWQiOiI1MjM3Iiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjUyMzcifV19XX0sIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmNyZWF0ZV9lbGVjdGlvbithLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.M7os6kWU5Ijp5zBWRE94BvwQ2_iS_fSq_nSKXQxA5G-GGt4X8Qwd2JjFcOhJ_jnIeIJE4-IGBEE3BSNeCXQ73VY2WokPKrMrhBiawepuDoNfjHmj_X5r48aGt6vy1A-u5-bJh0wQQvG6mSF9Xx2uHW8lY4aSgAEEIw9FCTLaqK4k38U0491A8f3u8Es4k4agY7ViBMdFCHWtsBt4bkAiUJXYe8OWucBXFSQA1C4JwsixAUPJ7LVpuAiYshIF-UPBrM-pe56bfslFTRIDY4pGRKQpJimZQfkP2kOoCRw6x4rgS8MXQ1L70pMqRq-pW39EHpcDN-nfgxNJwHxIk4GXUAeDTQT5cG5SjSVXUqiN5dhPWJZOofXQ4FyF1QEgGaBq_J3YJmT6jvNaj_8oUBEnJ8CHa3Yd963l2U7PdDdG6mRuhKRCWI3TbKfc3WoSmHosSTSD5ldexeH4DGs_-N-lO8UPQC5LFcbI0gxUnRa0oiWqkNIdqZx2pFwAsMtI5-afl7EsbnjFQlj6nruBKYcVGhraph5f4fAznHeJgutju7iUXPI2gmyemNcneXP8daNPxn6O0XEe52CDlTZl8V_rzmaizQ3BJNO3DsspRvS55lSsH_ce_3ElGMiFmf5cgt00RmUiTXjLN59gvIsZgQhfD7YxWXUdhkO1stSxCXuuT60","__typename":"LogEntry"},{"id":"8","messageId":"decidim-test-authority.42.key_ceremony.step_1+t.trustee-1","signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzIsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTEiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0xXCJ9In0.aAFO-ql5DzCBeSUrHp-fYWuAzYFDmbaGFfdcP09_yPoWdE-BwsCO9XB43jyjLdDINX-BhkaXClh1NZVtoF5ZjtQINYFu6xG53cd00IUqfKAr9dEMiIqI2msrzo1FvHxnYs_r5b8zKFCWpFTIi6iyROAeOQP0Tkjge8zJRXdEW185SxdwxG5iDLxZWNy-_3fkVIJWKxoS8rB7gdGe-9oDaVJNYcTCz-s-vhfDwjwuIQm1xuT4POkFunlilrnhP_NZ3uQtVMRzOgvVsOXXyl5y0WgFO9J48xCgryIuzZ3puPFRyRuMjJtSdC3BW_g8eOimnN8pTxXDZxIV1SU0HUAG0__6jIzw65dZf32X7g1wuMZEUMCegfv2oQfCNt51aE8aV7P1gszXK1dXtOm4mdxE2O6Cs-KBnANYCl_jWGGyvI99Xul8lnj9sIB0bLR6tAcwOy065xtu3L7Kh_kYpD2g9Ze2YbOWvnBOPB4ry4ew46rHmFYC0j1tUnDtF4j04KRBHbYapjrxQ-Omc3syRZd1jA8ibCHoMcp5LPfS3PZ_-2V__qNu_5sqeLIpM2puhx2iN1nyKiaedDNgohKVVBNqgjHHhymwsmbjGF0coBXddBrVul1NHCmuJ2VjsbQAe3dys6amGIABewtqx9_aySBQ2Vr8VJP7Et9XTIKARUfz0sc","__typename":"LogEntry"},{"id":"9","messageId":"decidim-test-authority.42.key_ceremony.step_1+t.trustee-2","signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzYsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTIiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0yXCJ9In0.gyLqI2VyVNPYqpKgxDIh7maAW0hBwIzRg1yer2kzXuTuixN9SXpFdPzM5KJy9ZLudxHtVQzLDv67tWqMNtSYveYNRkgsZN9WGSm9X4sv0lhAbkASdpPlubhZN_zklyHg7_DYy2od6QvGPasmx1OoCMDGRodpZ-fWAmYW8u-p5Kyuzwjt8Ix20_yi_xuK8kkqBnW_qoyMDQyOTzOJSeAqpaU6ZcgOQwSn2AcbTVkrT9PTYLFV8nGKdV4AcmBYWWi89421GclQzAQ6SYNatTL6WjAdF6TYlq-LzaBsBxji4Q1p7vnn6kamCcUOmgYilDwi7mshLBqJ-iE8prsIifXniwbAirokoGLDhQzxPsC4un8sckW75o3MXqo1vL-hJ7qidlWivoFnzMo-OfxByTPT97y61xgIwDXA5RZ_LbBlBWn5FUygQkAgd0NdCw1IcESVoeAIFIsjiPBvF0-jZO7jydcJdGazyhAYQdhd3yKRDMm3o-_7EiMn4YartwglwhD8wwGK2QOOjbL0wyk0PJZZqhU2WxXvf13SJCWTlaOCue0-Pgz9JbZtMN0RoueFCivsNdvGwqBNIzbtOnZ497k0VjUXgtGtxdoBEnnhnImF8z8L0Oe-VvFsuSTM48YR2MGjil_Ezd1U-InIG9YvO8PIi-aVa3iLz5QmlvirgtyfTvw","__typename":"LogEntry"},{"id":"10","messageId":"decidim-test-authority.42.key_ceremony.joint_election_key+b.bulletin-board","signedData":"eyJhbGciOiJSUzI1NiJ9.eyJtZXNzYWdlX2lkIjoiZGVjaWRpbS10ZXN0LWF1dGhvcml0eS40Mi5rZXlfY2VyZW1vbnkuam9pbnRfZWxlY3Rpb25fa2V5K2IuYnVsbGV0aW4tYm9hcmQiLCJqb2ludF9lbGVjdGlvbl9rZXkiOjQ5fQ.nMUD1KdsGn7YmChI_vhUPvkOo5_ftKeH02Sw_dfKm1zZM7NcxH2MtS7-kebPNfavCsQkQwU-khIQyahYTurO-u5jPv0saolUMLfDEWbWf1E3fC2KAeFf8W6ysx8djFMQYm6z6Zyf0mfEbylIA82yuTLqXCBDlMSQLKO8CNr7nvf8fk8P4Rta4oUoqPYgzGiXSRVn5ROmdYw8Hox5ewyo1D2DwrEpJhVwCbSr9gun-bOZ63eR5fOcfn0a_-KK5Kxyp030EzWIVwgLOHoSf7TdWOLM2fRTfQOK7dtl0guet-830Xrfv_XaWUrWQLnB1Lf66mG0Gku7RgTiLv-z9h_bNupmQoWcOut2OHtJhccRAWum1NkCkN5SkkYUAmFvio0Bw537hvGyJ8U6yf04p8QVrzQIoOW2t_AtKZdgtUSKA2usFJM0Gkq_Jt4Z7khUvO05pUJtgt5tkEcshF_maJH2gIgqGpgnusvGeSJ0Gg_A9WwYVVia9toqMags6bwT79T4LcuYwqU8ZdWFvA66kdN582AEzjS4z49XfixEFFEakmuziOj8JJ12AZ3FI4tAg1qZa42w-gAiNs6gJWhUbs5wuf9vFCypa3D15wleC3i6KfHp2aixlIhNa880D8p1wbssTZvCqsg9l7qC7VPpSCKUfQ24-laP846Mh_Q2ibn5fz8","__typename":"LogEntry"}],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5e3ce30b2aa48003365093dcf3ba0ac9e20321b5.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5e3ce30b2aa48003365093dcf3ba0ac9e20321b5.yml new file mode 100644 index 0000000000000..59dcf43cb9475 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_1a79ec0cf562a12d8981b663d65e2aee8d82fbba_5e3ce30b2aa48003365093dcf3ba0ac9e20321b5.yml @@ -0,0 +1,28 @@ +--- +:scope: complete ceremony with trustee 1 +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":"10"},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"14668fa8732050881aef2ce979bf1528" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 925b03bf-05e0-419a-bd2c-989d053c3e41 + X-Runtime: '0.016016' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_545694844d1d47a77f1f226fd9aaf0cb60393c67.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_545694844d1d47a77f1f226fd9aaf0cb60393c67.yml new file mode 100644 index 0000000000000..4e97e5c32d0c8 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_545694844d1d47a77f1f226fd9aaf0cb60393c67.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 1 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":"7"},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"14668fa8732050881aef2ce979bf1528" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: 117b108b-e82c-4ce0-81b7-47cb7f03043a + X-Runtime: '0.016815' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml new file mode 100644 index 0000000000000..26a9527bcc1ef --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_5950dee325bc389e5d97db961ec341cdc4ad4bd8.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 1 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"GetElectionLogEntries","variables":{"electionUniqueId":"decidim-test-authority.42","after":null},"query":"query + GetElectionLogEntries($electionUniqueId: String!, $after: String) {\n election(uniqueId: + $electionUniqueId) {\n logEntries(after: $after) {\n id\n messageId\n signedData\n __typename\n }\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"020ea67b8532418ce52160ba115b115f" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: bcafd616-94d9-4466-b0fa-9136a1dc95cb + X-Runtime: '0.019691' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"election":{"logEntries":[{"id":"7","messageId":"decidim-test-authority.42.create_election+a.decidim-test-authority","signedData":"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MTA1NjA4MjQsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJUcnVzdGVlIDEiLCJwdWJsaWNfa2V5Ijoie1wiYWxnXCI6XCJSUzI1NlwiLFwiZVwiOlwiQVFBQlwiLFwia3R5XCI6XCJSU0FcIixcIm5cIjpcInJIcE5WT3ZwTHJkTU1BV3NKaDQyVUtubjVzQVVSaEI3QS1zRVFlMGhROGdrNWIzdFZ1ZmFiQUpJc2ZFQXJzanl0OTFVSVhnbVNueUdkOHRia0VyczUzRlljTFhnWndERUoxTk5jQmRIaV9pR0pVTEYxSU9LaHhXSEpTMVFLamtQb1NhbUNUZW9ROGhXcTBtZFdIRjFnc2lPdFJqSWZ0LVJYN0FtMlpxazUzQ2VQRTJUVlJxbkh1RW1NUUhRLWxjUDJQYmUxN2NUNGk2UG95REl1YW5iYWJlbkRrdE56R1hHN0JmRDRPN3poTVdCMGRFMVNRWlNSMDlkYW5Dem81M3hrNE5sb1Y5eENSOVZjU25HdXMtQW5LSVpRWkZYaTBRcUlLNmk4VkZOdUE5UFlDQ2ZsZVVXQ0F5SFJRTWtkOVhTemJUczhPZVdBdmc0UmNiOFd2NUdrTDhhY2F6eTdHMGRpaUF2b3RJcjA1OENOb0ZHcXZ0dUtueGlsUDJhcGFZd0NCVGEtU1FwNERxQ2lZY0F0UGpfV2VSVGJhcDZXeURjZnl4TDVMeVVReXNWbUduNGZCNG5MVm9EV3g0elZtVncwU2JHUVQ4SzRGbWtJaGp0ZERtRUxQYWllbnRHOFRlcDRJQmpQdEZTYS1STUxBdTQ1akhkbHNYQlpqbXJiZVRid1RCV1Y0YVNLcDZUMmxjRU9jLW14cm9ycldnVEdZeTJNdnBKSExhbmVleGVmRU1WQjhpWEpKWDJMNEpBcUtDd3E3VjhDd3VtMFpzVFdhS0xzR1JOcVBlQXdheUFKZVh1aVQ1aERzM2ktY3V5dXFyYzJnbVd4dTVGRUdaSEpRV0dfTUxuWUFUWV9ubElEcS1vdnVHR0phLWxzenU2cHlWdkNLcnlDNzBta3NNXCJ9In0seyJuYW1lIjoiVHJ1c3RlZSAyIiwicHVibGljX2tleSI6IntcImFsZ1wiOlwiUlMyNTZcIixcImVcIjpcIkFRQUJcIixcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCJtWVc0cHk1NWVIVnpsQU9MZVdTbGtYLVV6RmNXbVBaTmd0MzhZZ2ZEc3JXc01pRjk5TnhlUzFoSEJCQkFJTVlWajREZHJhdW54d3hpYkZaSm00QjdKZkxlZE8wMnBHSFhXcHgtVlVTd1JVN1RLQk84S0djTEl1eUlzTEFuNFZabWNVQ2ZPRDR6NEQzbU9QWEpGcm15X2RiNEVRSmdRbmtSUWRvcDlOYi16RXhqWWR3OUlPblNGWWRZTEo2ay1nNE9udTQza1VJVkRqQkJ3QmxOSElrci16cEFwUFdoQUJVcFNWX1Z5NldBZGt1M2l4NE15RlFMUTQ4bGpKcDRLVktydzg1a20zeDVrMXA3eVhwUlhhZUFVd2RsMXVjWlVIZ2ViMjNFRC1xMmd3QVNBYnJCTE9NbHJyUnJleE9iUlhsSGQ3Z2tRUzRmd3ZMdGVoRU5ZMEVlcGFTNlI5WkRHRDVsOEFKT2xTM2pHQ0JmUWRwTDlhSlQwNVpxbUI4VmkwRmo4UGdEa3pzZWxxcDllU3dVQmI4MmJwOHBhZFg5TnNaUGFva1hMdEpsczhhY1NPUHBCRHdtRVBBaVVNTDdMV1V3aVJ5bGltSzRDVkROeHVsTkduQnVQbFlsNWI0dlpRSHRwakJFRmZLVVp4SThxWTFZMnFwRWVoYVlEMG9YYTY0eXIySGk1SVZkbENPUzg4aHFQc2tQdHRvZTI4Y202WVZZZmxKUDlidjRzbHFldTFjYWZEU0N4WDdBNFBYODN3RHBXNWd5V0MzY1hBUFRYaDJQbHhsbm4zcDlLVGFlNl9ZUGtJOElkMjZ6a202MUxwS2ktQ2tRQ3ZvMmZIdmZaSTIzSmlTajNZOUtZWlIyNVltOE85YWFOQmRoZ3ZOS2hCOFZGbGl6S2V0M1ZVOFwifSJ9XSwiZGVzY3JpcHRpb24iOnsibmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkxhYm9yaW9zYW0gdmVsaXQgdm9sdXB0YXRpYnVzLiA1OCIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMS0wMS0xNCAxODowMDoyMCBVVEMiLCJlbmRfZGF0ZSI6IjIwMjEtMDEtMTYgMTg6MDA6MjAgVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSGFydW0gY3VwaWRpdGF0ZSB0ZW1wb3JlLiAxNDUiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJWb2x1cHRhdGUgbmVtbyBldW0uIDE2MyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkRvbG9yZXMgdm9sdXB0YXR1bSBsYWJvcnVtLiAyMTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJSZXByZWhlbmRlcml0IGRlbGVjdHVzIGV0LiAyMTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTZXF1aSBwYXJpYXR1ciBpbGxvLiAxNTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlhIHN1bnQgdXQuIDIwMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFtZXQgYXJjaGl0ZWN0byBhc3N1bWVuZGEuIDIyNiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNhZXBlIHZvbHVwdGF0aWJ1cyBuYW0uIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b3MgY3VtIHNpdC4gMTI3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTmFtIGl0YXF1ZSBlbmltLiAxNzgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjE2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJbmNpZHVudCBhY2N1c2FtdXMgZG9sb3JlbXF1ZS4gMTM5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxOSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVXQgdXQgdml0YWUuIDE0OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2lsaXMgdXQgcXVpLiAxMzYiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIzIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJDb25zZWN0ZXR1ciBxdWlhIGVhcnVtLiAxNjAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaXQgYXV0IHF1aXMuIDE5NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFzcGVyaW9yZXMgZnVnYSBldC4gMjIzIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIzOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTm9uIHF1YWUgb21uaXMuIDIzNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBsYWJvcmlvc2FtIGRlbGVjdHVzLiAxNTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXRlbSBwb3JybyBzaXQuIDE5MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNpbnQgZGlzdGluY3RpbyBtYWduYW0uIDEyNCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlJlcnVtIGV4cGVkaXRhIGRvbG9yZS4gMTcyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpcyBlbmltIGludmVudG9yZS4gMTg0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiT21uaXMgbWFnbmFtIHZvbHVwdGF0ZW0uIDE0MiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b2Qgdml0YWUgdXQuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkJsYW5kaXRpaXMgZGljdGEgZXQuIDIyMCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Iklwc2EgZG9sb3JlcyBpbnZlbnRvcmUuIDIzMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1YXNpIHZvbHVwdGF0ZW0gZmFjZXJlLiAxMjEiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzczIiwic2VxdWVuY2Vfb3JkZXIiOjEsInZvdGVfdmFyaWF0aW9uIjoib25lX29mX20iLCJuYW1lIjoiUGFyaWF0dXIgZWFxdWUgdXQuIDExNSIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJQYXJpYXR1ciBlYXF1ZSB1dC4gMTE1IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPlN1c2NpcGl0IHF1byBlb3MuIDExODwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMTQiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNTIxNCJ9LHsib2JqZWN0X2lkIjoiNTIxMyIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjEzIn0seyJvYmplY3RfaWQiOiI1MjEyIiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6IjUyMTIifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc1Iiwic2VxdWVuY2Vfb3JkZXIiOjIsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkF1dCBhdXRlbSBhY2N1c2FtdXMuIDE2OTwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMzAiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzMCJ9LHsib2JqZWN0X2lkIjoiNTIyNiIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI1MjI2In0seyJvYmplY3RfaWQiOiI1MjI5Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMjkifSx7Im9iamVjdF9pZCI6IjUyMjgiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNTIyOCJ9LHsib2JqZWN0X2lkIjoiNTIyNSIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjI1In0seyJvYmplY3RfaWQiOiI1MjI3Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjUyMjcifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc0Iiwic2VxdWVuY2Vfb3JkZXIiOjMsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJudW1iZXJfZWxlY3RlZCI6MTAsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RXQgdXQgdGVuZXR1ci4gMTMzPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjE4In0seyJvYmplY3RfaWQiOiI1MjI0Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6IjUyMjQifSx7Im9iamVjdF9pZCI6IjUyMjEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIyMSJ9LHsib2JqZWN0X2lkIjoiNTIxNiIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI1MjE2In0seyJvYmplY3RfaWQiOiI1MjE5Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjUyMTkifSx7Im9iamVjdF9pZCI6IjUyMTUiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIxNSJ9LHsib2JqZWN0X2lkIjoiNTIyMyIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI1MjIzIn0seyJvYmplY3RfaWQiOiI1MjIyIiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjUyMjIifSx7Im9iamVjdF9pZCI6IjUyMTciLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiNTIxNyJ9LHsib2JqZWN0X2lkIjoiNTIyMCIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjIwIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6Ijc3NiIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJWb2x1cHRhdGVzIGNvcnJ1cHRpIG5vYmlzLiAyMDgiLCJudW1iZXJfZWxlY3RlZCI6OCwidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiVm9sdXB0YXRlcyBjb3JydXB0aSBub2Jpcy4gMjA4IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkFzcGVybmF0dXIgaXBzYW0gcXVpcy4gMjExPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIzMiIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjMyIn0seyJvYmplY3RfaWQiOiI1MjMxIiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjUyMzEifSx7Im9iamVjdF9pZCI6IjUyMzUiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzNSJ9LHsib2JqZWN0X2lkIjoiNTIzNiIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI1MjM2In0seyJvYmplY3RfaWQiOiI1MjM0Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMzQifSx7Im9iamVjdF9pZCI6IjUyMzgiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIzOCJ9LHsib2JqZWN0X2lkIjoiNTIzMyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjMzIn0seyJvYmplY3RfaWQiOiI1MjM3Iiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjUyMzcifV19XX0sIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmNyZWF0ZV9lbGVjdGlvbithLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.M7os6kWU5Ijp5zBWRE94BvwQ2_iS_fSq_nSKXQxA5G-GGt4X8Qwd2JjFcOhJ_jnIeIJE4-IGBEE3BSNeCXQ73VY2WokPKrMrhBiawepuDoNfjHmj_X5r48aGt6vy1A-u5-bJh0wQQvG6mSF9Xx2uHW8lY4aSgAEEIw9FCTLaqK4k38U0491A8f3u8Es4k4agY7ViBMdFCHWtsBt4bkAiUJXYe8OWucBXFSQA1C4JwsixAUPJ7LVpuAiYshIF-UPBrM-pe56bfslFTRIDY4pGRKQpJimZQfkP2kOoCRw6x4rgS8MXQ1L70pMqRq-pW39EHpcDN-nfgxNJwHxIk4GXUAeDTQT5cG5SjSVXUqiN5dhPWJZOofXQ4FyF1QEgGaBq_J3YJmT6jvNaj_8oUBEnJ8CHa3Yd963l2U7PdDdG6mRuhKRCWI3TbKfc3WoSmHosSTSD5ldexeH4DGs_-N-lO8UPQC5LFcbI0gxUnRa0oiWqkNIdqZx2pFwAsMtI5-afl7EsbnjFQlj6nruBKYcVGhraph5f4fAznHeJgutju7iUXPI2gmyemNcneXP8daNPxn6O0XEe52CDlTZl8V_rzmaizQ3BJNO3DsspRvS55lSsH_ce_3ElGMiFmf5cgt00RmUiTXjLN59gvIsZgQhfD7YxWXUdhkO1stSxCXuuT60","__typename":"LogEntry"}],"__typename":"Election"}}}' diff --git a/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_8265d3f245855576e2457947159c4d8d59ecfaba.yml b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_8265d3f245855576e2457947159c4d8d59ecfaba.yml new file mode 100644 index 0000000000000..b7e1aa09aa885 --- /dev/null +++ b/decidim-elections/spec/billy/when_performing_the_key_ceremony/generates_backup_keys,_restores_them_and_creates_election_keys/post_bulletin-board.lvh.me_f2b0e418ac84da1f9f759ee4d92d0f97eb922515_8265d3f245855576e2457947159c4d8d59ecfaba.yml @@ -0,0 +1,28 @@ +--- +:scope: trustee 1 download +:url: http://bulletin-board.lvh.me/api +:body: '{"operationName":"ProcessKeyCeremonyStep","variables":{"signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzIsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTEiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0xXCJ9In0.aAFO-ql5DzCBeSUrHp-fYWuAzYFDmbaGFfdcP09_yPoWdE-BwsCO9XB43jyjLdDINX-BhkaXClh1NZVtoF5ZjtQINYFu6xG53cd00IUqfKAr9dEMiIqI2msrzo1FvHxnYs_r5b8zKFCWpFTIi6iyROAeOQP0Tkjge8zJRXdEW185SxdwxG5iDLxZWNy-_3fkVIJWKxoS8rB7gdGe-9oDaVJNYcTCz-s-vhfDwjwuIQm1xuT4POkFunlilrnhP_NZ3uQtVMRzOgvVsOXXyl5y0WgFO9J48xCgryIuzZ3puPFRyRuMjJtSdC3BW_g8eOimnN8pTxXDZxIV1SU0HUAG0__6jIzw65dZf32X7g1wuMZEUMCegfv2oQfCNt51aE8aV7P1gszXK1dXtOm4mdxE2O6Cs-KBnANYCl_jWGGyvI99Xul8lnj9sIB0bLR6tAcwOy065xtu3L7Kh_kYpD2g9Ze2YbOWvnBOPB4ry4ew46rHmFYC0j1tUnDtF4j04KRBHbYapjrxQ-Omc3syRZd1jA8ibCHoMcp5LPfS3PZ_-2V__qNu_5sqeLIpM2puhx2iN1nyKiaedDNgohKVVBNqgjHHhymwsmbjGF0coBXddBrVul1NHCmuJ2VjsbQAe3dys6amGIABewtqx9_aySBQ2Vr8VJP7Et9XTIKARUfz0sc","messageId":"decidim-test-authority.42.key_ceremony.step_1+t.trustee-1"},"query":"mutation + ProcessKeyCeremonyStep($messageId: String!, $signedData: String!) {\n processKeyCeremonyStep(messageId: + $messageId, signedData: $signedData) {\n pendingMessage {\n signedData\n __typename\n }\n error\n __typename\n }\n}\n"}' +:status: 200 +:method: post +:headers: + Access-Control-Allow-Origin: "*" + Access-Control-Allow-Methods: GET, POST, PATCH, PUT + Access-Control-Expose-Headers: '' + Access-Control-Max-Age: '7200' + X-Frame-Options: SAMEORIGIN + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Permitted-Cross-Domain-Policies: none + Referrer-Policy: strict-origin-when-cross-origin + Content-Type: application/json; charset=utf-8 + ETag: W/"f943d692fd950e95c55db705b76a7182" + Cache-Control: max-age=0, private, must-revalidate + X-Request-Id: a74b1c76-9f52-498f-9885-701e0a0425c3 + X-Runtime: '0.065003' + Vary: Origin + Connection: close + Transfer-Encoding: chunked +:content: '{"data":{"processKeyCeremonyStep":{"pendingMessage":{"signedData":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTA1NjA4MzIsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmtleV9jZXJlbW9ueS5zdGVwXzErdC50cnVzdGVlLTEiLCJjb250ZW50Ijoie1wiZWxlY3Rpb25fcHVibGljX2tleVwiOjcsXCJvd25lcl9pZFwiOlwidHJ1c3RlZS0xXCJ9In0.aAFO-ql5DzCBeSUrHp-fYWuAzYFDmbaGFfdcP09_yPoWdE-BwsCO9XB43jyjLdDINX-BhkaXClh1NZVtoF5ZjtQINYFu6xG53cd00IUqfKAr9dEMiIqI2msrzo1FvHxnYs_r5b8zKFCWpFTIi6iyROAeOQP0Tkjge8zJRXdEW185SxdwxG5iDLxZWNy-_3fkVIJWKxoS8rB7gdGe-9oDaVJNYcTCz-s-vhfDwjwuIQm1xuT4POkFunlilrnhP_NZ3uQtVMRzOgvVsOXXyl5y0WgFO9J48xCgryIuzZ3puPFRyRuMjJtSdC3BW_g8eOimnN8pTxXDZxIV1SU0HUAG0__6jIzw65dZf32X7g1wuMZEUMCegfv2oQfCNt51aE8aV7P1gszXK1dXtOm4mdxE2O6Cs-KBnANYCl_jWGGyvI99Xul8lnj9sIB0bLR6tAcwOy065xtu3L7Kh_kYpD2g9Ze2YbOWvnBOPB4ry4ew46rHmFYC0j1tUnDtF4j04KRBHbYapjrxQ-Omc3syRZd1jA8ibCHoMcp5LPfS3PZ_-2V__qNu_5sqeLIpM2puhx2iN1nyKiaedDNgohKVVBNqgjHHhymwsmbjGF0coBXddBrVul1NHCmuJ2VjsbQAe3dys6amGIABewtqx9_aySBQ2Vr8VJP7Et9XTIKARUfz0sc","__typename":"PendingMessage"},"error":null,"__typename":"ProcessKeyCeremonyStepMutationPayload"}}}' diff --git a/decidim-elections/spec/cassettes/Admin_manages_election_steps/close_the_ballot_box/performs_the_action_successfully.json b/decidim-elections/spec/cassettes/Admin_manages_election_steps/close_the_ballot_box/performs_the_action_successfully.json new file mode 100644 index 0000000000000..97245a6e882bb --- /dev/null +++ b/decidim-elections/spec/cassettes/Admin_manages_election_steps/close_the_ballot_box/performs_the_action_successfully.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_217600 {\\n closeBallotBox(messageId: \\\"decidim-test-authority.10002.close_ballot_box+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2MzcyNDYsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAyLmNsb3NlX2JhbGxvdF9ib3grYS5kZWNpZGltLXRlc3QtYXV0aG9yaXR5In0.UoSfKoT80kShvwOvWHD-hsLUooocMijOxhcAvhFjtWGGUkQ_uNr2dqoiNqbtiUAn1yPeX2LvPcNwqeaLNCKt9sGvLViVmQPZqrx5J7xeRE8opUES71zcmXvBL6ze_W4JQN4fk8XgoaLSLhcMZzFI9w5R2dszn8muIAyZ2T-Vkg3dMB0RRthHCRGuCYCDpwiSeZOPyCuZbXN6RitiD02RmuyK-BjUlldgjrPgeBKCY7yI2lIL3hniymtcfA1GGbDvRGCGfdWRBp5bpk-Lbmjc7NvewqOa6eYnTbIs_eWlznkDVSIcoi5y8e2bVIhDCNlbCt5WkKUWcEppWsV5rUG5-6XAhRxaxmZ_ge58os4WE3N0PohTKa7hFKC8uoGwSmfw-biTerYKVLAUgQC8s3wVbBIhMNxli48NSFAs_xF2GJdBvOseqfy5q9hN_jqeCU7cR8VgEw6YXAUX8HTPvbqe_MurSlLELaUonfsjL8WyDVtcOid25nfaZlsMsFp8wI7aUfkmOKN3fUSxzKfMopLr2lSoWOg2k_zXCrZ6AurSdatj0sI6nRoZlf-2H5kl-5BY74-_BuTpLJ-HlVEDyzh9x-WFsCncly79dDBCgYOyEEjZ9Fk2mqOsWntS0D_hGutAUA5fzI9rGvuchWvqWhrjaoqUl8If9e7ZAbv-I0YaGqs\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_217600\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"d88c83daf267765e702b4d923de5f6a0\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["450c8fbc-1239-4e0f-ac87-2e4b59ebd860"],"X-Runtime":["0.086515"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"closeBallotBox\":{\"election\":{\"status\":\"tally\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 11:40:46 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/Admin_manages_election_steps/open_the_ballot_box/performs_the_action_successfully.json b/decidim-elections/spec/cassettes/Admin_manages_election_steps/open_the_ballot_box/performs_the_action_successfully.json new file mode 100644 index 0000000000000..ff8ba1f7acde2 --- /dev/null +++ b/decidim-elections/spec/cassettes/Admin_manages_election_steps/open_the_ballot_box/performs_the_action_successfully.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_212300 {\\n openBallotBox(messageId: \\\"decidim-test-authority.10001.open_ballot_box+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2MzcyMzYsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAxLm9wZW5fYmFsbG90X2JveCthLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.j9bIq_VLI6vg1DkWLQOLwFP7pGnNn0im2Mn4yR-VFcv9uHqwLtdWEXS_VnKug9qZFJUhbVAdZzuqEZDJkitkwqLmNVUnB1EFLqcmT_ay3f63ityVQpGec5rs660ZMHwwNIPFhyTjfmktUAxynjGpOMrSEpsBx4EzI74_5XFZkGdqJrLpdlxxG911P1Bdai--_VxXw4m1XgctNGmWG6YWgl6JM78uKih9j54hQ-ZB1wkDY0p0yBEf9hDdUSFx3ArSfCAN4yK6WWvM9L8gJl730mczP253lw7OisxCvppeDjLWqaGumsHGIrjOmiY0RAh33fLFAl6uqGj6zUIKulSLYelz799tcWc8JdobYhszFaAX5rNfnmj12V6tN9A35OdeXYR6Nnk92Yhrf4pTSik7ZDVj2H2h2Eh3ktZEbe07NBbwmmGSMDqokJvysk5sgWNCMCU0fwwjL-RHevODuRSYHmPxxz0FUykXbil0BFMrhNP7WJ7Xim3VGG41hG58qZzyek7EoFXmQOQWLcMbxHqTm3wDYf9r4CZopw509C_yQCpd8tST52zcEfa2_uIleAcGd5PRBRnQQxrdoZJ2-tmiGt5zECciFXUiM5uR1R2dV5B_cYDRS5249r_XuX1Tm0MEt7PPeUNC5Z0in1NK0WPBQpo7gt-YdJQXnX73Cuk5TAo\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_212300\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"848ecc80a77c81d45e9e1704860ef0f5\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["1492767e-7322-4df4-84bf-749d185d2224"],"X-Runtime":["0.666062"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"openBallotBox\":{\"election\":{\"status\":\"vote\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 11:40:38 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/Admin_manages_election_steps/setup_an_election/performs_the_action_successfully.json b/decidim-elections/spec/cassettes/Admin_manages_election_steps/setup_an_election/performs_the_action_successfully.json new file mode 100644 index 0000000000000..9a78f5529ac69 --- /dev/null +++ b/decidim-elections/spec/cassettes/Admin_manages_election_steps/setup_an_election/performs_the_action_successfully.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_220760 {\\n createElection(messageId: \\\"decidim-test-authority.15.create_election+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2MzcyNTMsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJTZW4uIE1hZGVsZWluZSBLZW1tZXIiLCJwdWJsaWNfa2V5Ijoie1wia3R5XCI6XCJSU0FcIixcIm5cIjpcIjZsc25Ea2RoRDNvOFJJMGJqZ1N3YkpGTkJiMF9HdjdSbjJqUFFORjFyR284cDViYzFwMTlORUdpeFlia1YybXNRRVN4X2Z6VWxZd0p4aW8wbmhhZk9TTGJvemVSMVc3QWxrUDBFTkZCMzE2aVY5SnV5dXdhRnlEOU92dXl5NkxTWlo4ckhNeDBYRU84UkQwWTdZSk9JV0VDV1AxUkpSTXFST3FBR0hzckxtM29wcTJHZkx5aGhUZTA2SEZvNW1sd2lQQXpFSGVCT3NScno2RUZwalM4dVBSQkN1cVgtdmFzaVN5eExsN0xwYW9HZE91WW5DeFBlTkJKajlFNU5TTUVoNm5BWXJCaFN5SEpTckR4bnhYcUFvbXRYQ3BBX214eFktd3V2TXNQVzhuZkpTeWJBbHdJcTVVYnh1TjhzWDNSdUhQSmF2eGFPWG4yTlF5N2luQjFoYkIzbjV0cEpBdUU3MWwwQWtCb2NRaHBRRmQ1MlBLNy1HQ2c4SERTWVE3WGFmVWZMckFidzhnMFNpaUlNYTZEMlJDZzdBOTZHbTBOVnpjakVLZ0NIZWZmUmJ3T2ZGZEhFelJtaVloY2FjMWQ0THBXenNKaEFLeFFjeTM4UkZjVk5qWkFZaW1QVEh6Z3d2NmZXSVNtNWVEanExeW1DbnZKZjVITTFmT0hqcG9URGNkT25xYjczeFJ1RmR5d2dVZUNjd3ZJNklWbUp0VkUyTUNCc2lqbUMyZ2RvTWJlQjdPbU1kdlpJUlU3d0xaUENUdmZ1MEZnWUZWODBDTVZ5YWI5SV9CcFAzanpOTWFPLWF4NVhzV1g1d2hnY3lEOGNBT2syVTB5aFB2bnJ6Q1lhSVpyeWF0UGV0UkFJOVh2NWtZUTZYVjNmaWlFenc3RWZCSW9EN2tqZTkwXCIsXCJlXCI6XCJBUUFCXCIsXCJraWRcIjpcIjMxZGMxM2IyZmVlYzVkNWFhOTM5MTYzZDk5YjRlMzFhYmYzZTE1MDhlMzcyZDFjOWFjZmU3NjgxNTYzODczZjBcIn0ifSx7Im5hbWUiOiJNcnMuIEtpcmsgTWlsbGVyIiwicHVibGljX2tleSI6IntcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCJ1OGdGUFBPWTlsNkpqOG5BOFNXZjRiZVdRX3dEeUloM3lidE9FRVhyeHE1bE5hT0hhZElHWjYzb0RtdnRoVnZ4a2syeDF4a1p4czVIS3hnTGpPTzFGZGU4VzdtZEpwRkp0Vk5QSVpXQXp4aUNPNm53UFIwTzNGcTVBNnpsOEpaYWl5bDlsa2diWDlXTEZkdW9kR19NNjhZMGV0Q3dhNWRtSEdRVTRvdV9reTA0c3BNa0tTS0RqeHZtSUpHVTA0TGlYUHBGUE9PQ0lqbVY1aHZoZE9MZl9EQkxlRzRHbWxPbi1nM2d0X0dzOXZjbVhacWpSTlVDNFh3RFIxNU9oVWUwbWlsZDdjNFFibldtS0VNY2huVGhiM21pMmJqb3BHTVlsOGl0bXVKQUtIQTBwX0ROX21DQnVGWjU2STIwMnlYV0dNa1JUZXBFeUtSMWN1Z29tQjBkbmsxekF2S0xYV3d4UG1HeDZVOTdUOVRGLTJBMnJkRms0VnBkc0ZNTDZvdi1iQVhBZTdNSmJGYWFsVUtjSlpDdnJXVzgwdVpYQnVXaXFHUjNqNlBlMW9pSy1YRG52UkdsdHFTM1dJdERlS0RMRl9meUItNlFfZ0RLS1E0bTRxbDl4YlhhZmxqeEt1RUlSLVNidy1fUWtqR052NEdudEZydFBTZjlXWkxoRENzdXNyQ0d2OEVqcG5nOUFXdFBrYnJJeFNkMWRGbkZjcWVhdWVkcHhOVXhIYjdNUTktZGw3b3pZSGl4aTFfYl9LRlg4Q2ZjLW1TN2hQZmtadXJSdnNJTmhCcEhCcEpSV3UtSktKRlIyTW91dFU4TWlWR3JIcmhiQ0k1MGRPNkRqZGNXVXlyY3ppZjJqYTA4SlZ0WWN5V3I5Ynh2QWNWSEJKUkR3WUtadHRiTlBna1wiLFwiZVwiOlwiQVFBQlwiLFwia2lkXCI6XCJkZDkxMzY5ZjdjNTk1ZGQxMTBhNDNkNmVkZTQxMDNjZmY3NTIxNTNjMmE5ZWUwNTU5NjE4ZGJlZTRmOTc3MWVlXCJ9In1dLCJkZXNjcmlwdGlvbiI6eyJuYW1lIjp7InRleHQiOlt7InZhbHVlIjoiU3VudCByZW0gZWEuIDc5MyIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMC0xMi0yMyAxMTo0MDo0OCBVVEMiLCJlbmRfZGF0ZSI6IjIwMjAtMTItMjUgMTE6NDA6NDggVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNDM3IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJOb3N0cnVtIGV0IG1vbGVzdGlhZS4gODc0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDM2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJOZW1vIGluY2lkdW50IGRpc3RpbmN0aW8uIDg3MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ0NiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRWFxdWUgaW4gbmVzY2l1bnQuIDkwNyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ0OCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSWxsbyBhdXQgbW9sZXN0aWFlLiA5MTkiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NTUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2VyZSBkb2xvcmVzIGV0LiA5NTgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NDUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZvbHVwdGF0dW0gYWNjdXNhbnRpdW0gbWluaW1hLiA4OTgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZvbHVwdGFzIHF1aSByZW0uIDk1NSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQzNSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVvZCBxdW9zIGV0LiA4NjIiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NTgiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2VyZSBtYWlvcmVzIGFsaXF1YW0uIDk2NyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ0MiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVml0YWUgcXVpIGRvbG9yZS4gODg5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDQ5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJOb24gbWFnbmFtIGFtZXQuIDkyNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ1MSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRXVtIGV4Y2VwdHVyaSBhdXQuIDkzNyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ0NCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiQ29ycnVwdGkgbW9sbGl0aWEgdm9sdXB0YXRlcy4gODk1IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDU2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJFc3QgY3VscGEgZG9sb3JpYnVzLiA5NjEiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NDMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlV0IGZhY2VyZSBvcHRpby4gODkyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDM0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJWZXJpdGF0aXMgYXJjaGl0ZWN0byBxdWlzcXVhbS4gODU5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDM4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlidXNkYW0gcXVpZGVtIG1vbGVzdGlhZS4gODc3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDM5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJPbW5pcyBldCBmYWNlcmUuIDg4MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ1NyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpcyBhZCBldC4gOTY0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDU5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJGdWdpYXQgdmVuaWFtIHByYWVzZW50aXVtLiA5NzAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NDAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IklkIGF0IHV0LiA4ODMiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NDEiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlV0IG51bGxhIGF0cXVlLiA4ODYiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NDciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBudW1xdWFtIHJlcGVsbGF0LiA5MTMiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0MzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZvbHVwdGF0ZW0gdm9sdXB0YXRlbSByZXBlbGxlbmR1cy4gODU2IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNDUwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaW1pbGlxdWUgc3VzY2lwaXQgZXQuIDkzMSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjQ1MiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTW9sbGl0aWEgaWxsdW0gbWluaW1hLiA5NDkiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI0NTMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkRvbG9yZXMgcHJvdmlkZW50IGV4ZXJjaXRhdGlvbmVtLiA5NTIiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNjUiLCJzZXF1ZW5jZV9vcmRlciI6MSwidm90ZV92YXJpYXRpb24iOiJvbmVfb2ZfbSIsIm5hbWUiOiJDb25zZWN0ZXR1ciB0b3RhbSBzZWQuIDg1MCIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJDb25zZWN0ZXR1ciB0b3RhbSBzZWQuIDg1MCIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc3VidGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiI8cD5WZXJvIGNvbnNlcXVhdHVyIGV0LiA4NTM8L3A-IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zZWxlY3Rpb25zIjpbeyJvYmplY3RfaWQiOiI0MzUiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNDM1In0seyJvYmplY3RfaWQiOiI0MzQiLCJzZXF1ZW5jZV9vcmRlciI6NywiY2FuZGlkYXRlX2lkIjoiNDM0In0seyJvYmplY3RfaWQiOiI0MzMiLCJzZXF1ZW5jZV9vcmRlciI6OSwiY2FuZGlkYXRlX2lkIjoiNDMzIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6IjY3Iiwic2VxdWVuY2Vfb3JkZXIiOjIsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IklkIGF1dCBkZXNlcnVudC4gOTAxIiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IklkIGF1dCBkZXNlcnVudC4gOTAxIiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkFsaXF1YW0gdm9sdXB0YXMgcmVydW0uIDkwNDwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjQ0NiIsInNlcXVlbmNlX29yZGVyIjoxLCJjYW5kaWRhdGVfaWQiOiI0NDYifSx7Im9iamVjdF9pZCI6IjQ0OCIsInNlcXVlbmNlX29yZGVyIjoxLCJjYW5kaWRhdGVfaWQiOiI0NDgifSx7Im9iamVjdF9pZCI6IjQ0OSIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI0NDkifSx7Im9iamVjdF9pZCI6IjQ1MSIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI0NTEifSx7Im9iamVjdF9pZCI6IjQ0NyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI0NDcifSx7Im9iamVjdF9pZCI6IjQ1MCIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI0NTAifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNjYiLCJzZXF1ZW5jZV9vcmRlciI6Mywidm90ZV92YXJpYXRpb24iOiJuX29mX20iLCJuYW1lIjoiUXVpIHN1bnQgcGxhY2VhdC4gODY1IiwibnVtYmVyX2VsZWN0ZWQiOjEwLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWkgc3VudCBwbGFjZWF0LiA4NjUiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RGVsZWN0dXMgcXVpZGVtIG5hbS4gODY4PC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNDM3Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6IjQzNyJ9LHsib2JqZWN0X2lkIjoiNDM2Iiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjQzNiJ9LHsib2JqZWN0X2lkIjoiNDQ1Iiwic2VxdWVuY2Vfb3JkZXIiOjIsImNhbmRpZGF0ZV9pZCI6IjQ0NSJ9LHsib2JqZWN0X2lkIjoiNDQyIiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjQ0MiJ9LHsib2JqZWN0X2lkIjoiNDQ0Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjQ0NCJ9LHsib2JqZWN0X2lkIjoiNDQzIiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjQ0MyJ9LHsib2JqZWN0X2lkIjoiNDM4Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjQzOCJ9LHsib2JqZWN0X2lkIjoiNDM5Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjQzOSJ9LHsib2JqZWN0X2lkIjoiNDQwIiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjQ0MCJ9LHsib2JqZWN0X2lkIjoiNDQxIiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjQ0MSJ9XX0seyJAdHlwZSI6IkNhbmRpZGF0ZUNvbnRlc3QiLCJvYmplY3RfaWQiOiI2OCIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJOaWhpbCBhY2N1c2FudGl1bSByZXJ1bS4gOTQzIiwibnVtYmVyX2VsZWN0ZWQiOjgsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Ik5paGlsIGFjY3VzYW50aXVtIHJlcnVtLiA5NDMiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-TWFpb3JlcyBhcGVyaWFtIGlwc2FtLiA5NDY8L3A-IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zZWxlY3Rpb25zIjpbeyJvYmplY3RfaWQiOiI0NTUiLCJzZXF1ZW5jZV9vcmRlciI6MSwiY2FuZGlkYXRlX2lkIjoiNDU1In0seyJvYmplY3RfaWQiOiI0NTQiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNDU0In0seyJvYmplY3RfaWQiOiI0NTgiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNDU4In0seyJvYmplY3RfaWQiOiI0NTYiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNDU2In0seyJvYmplY3RfaWQiOiI0NTciLCJzZXF1ZW5jZV9vcmRlciI6NywiY2FuZGlkYXRlX2lkIjoiNDU3In0seyJvYmplY3RfaWQiOiI0NTkiLCJzZXF1ZW5jZV9vcmRlciI6NywiY2FuZGlkYXRlX2lkIjoiNDU5In0seyJvYmplY3RfaWQiOiI0NTIiLCJzZXF1ZW5jZV9vcmRlciI6OSwiY2FuZGlkYXRlX2lkIjoiNDUyIn0seyJvYmplY3RfaWQiOiI0NTMiLCJzZXF1ZW5jZV9vcmRlciI6OSwiY2FuZGlkYXRlX2lkIjoiNDUzIn1dfV19LCJtZXNzYWdlX2lkIjoiZGVjaWRpbS10ZXN0LWF1dGhvcml0eS4xNS5jcmVhdGVfZWxlY3Rpb24rYS5kZWNpZGltLXRlc3QtYXV0aG9yaXR5In0.gCWIc0C-p1QE8vyz4cNvWcD2s7sHUT5yyYwQLmzHdNNzIiJUS-rAjT0Q_R8FCjzIHPwSHanEWeMR5KPuwa0bQbk65xkQUT698TGGVVNupEYkPKZefGXUoks6H_chnVfLnqrcmUiMuP_-mmMAw5razvwgWcUFEEtTciwNs9H1e0jgexojwtSD96ZrcN-fK8QphO17cOPNAfxG82hUuZ4usHbvKUA_leDvin4rALx_LP2sZDsIdf1AHodpaCT4ddlLzC0WI_IfOcqUabMiWbR509cpssFocgoiXJ7zNi9llHFWWLJrNDE3ZEkdiqlRae5mUhCzhO4iG8wcDg8ygxcJaZfzKZFYTLCOHpgvenjRZKQ1Xj_4tKlYr5Hhv8ViwazjkH_cQctYA8GLtIK3o8eienZKycg3-erTkjC8mb0yQJmFJ6rKx6xdX3jIPmzej_0lbSB8K5desy7irpoAWpg-ennW7J2e4A8Tj2Y2PCxDmHRRJLFwa5WhliOEbb8O3qIxjJMlJad-SkrMCeSG2QFptm2J0VR9toQ2NxVCEwDHZc3gKnJ4IaY0DRjsmoNTb12LKOj4FdxKkJKgx_0QHa9xTlfKc5RA-GZdNSAsn83ETGNMdDNsG84ZdDQ6ABq322So68CIl2ahoagi5p1kQtOAMCxoGL6dzs-P6_wQV-OeRnk\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_220760\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"254cb984da3479e99fd26dc4dc425048\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["4c550d70-0ede-4891-b590-cbb18955f552"],"X-Runtime":["0.371939"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"createElection\":{\"election\":{\"status\":\"key_ceremony\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 11:40:54 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/Admin_manages_elections/set_up_an_election/when_the_election_is_published/sets_up_an_election.json b/decidim-elections/spec/cassettes/Admin_manages_elections/set_up_an_election/when_the_election_is_published/sets_up_an_election.json index 09ca0a841be06..5e255c734c1a7 100644 --- a/decidim-elections/spec/cassettes/Admin_manages_elections/set_up_an_election/when_the_election_is_published/sets_up_an_election.json +++ b/decidim-elections/spec/cassettes/Admin_manages_elections/set_up_an_election/when_the_election_is_published/sets_up_an_election.json @@ -1 +1 @@ -{"http_interactions":[{"request":{"method":"post","uri":"http://localhost:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"query IntrospectionQuery {\\n __schema {\\n queryType {\\n name\\n }\\n mutationType {\\n name\\n }\\n subscriptionType {\\n name\\n }\\n types {\\n ...FullType\\n }\\n directives {\\n name\\n description\\n locations\\n args {\\n ...InputValue\\n }\\n }\\n }\\n}\\n\\nfragment FullType on __Type {\\n kind\\n name\\n description\\n fields(includeDeprecated: true) {\\n name\\n description\\n args {\\n ...InputValue\\n }\\n type {\\n ...TypeRef\\n }\\n isDeprecated\\n deprecationReason\\n }\\n inputFields {\\n ...InputValue\\n }\\n interfaces {\\n ...TypeRef\\n }\\n enumValues(includeDeprecated: true) {\\n name\\n description\\n isDeprecated\\n deprecationReason\\n }\\n possibleTypes {\\n ...TypeRef\\n }\\n}\\n\\nfragment InputValue on __InputValue {\\n name\\n description\\n type {\\n ...TypeRef\\n }\\n defaultValue\\n}\\n\\nfragment TypeRef on __Type {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\",\"operationName\":\"IntrospectionQuery\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"5176811497b3ee2cc43e68dbbe006fd3\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["167fc6c2-7bf2-4c70-a9eb-913a470f239c"],"X-Runtime":["0.066465"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"__schema\":{\"queryType\":{\"name\":\"Query\"},\"mutationType\":{\"name\":\"Mutation\"},\"subscriptionType\":{\"name\":\"Subscription\"},\"types\":[{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"description\":\"Represents `true` or `false` values.\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"Client\",\"description\":null,\"fields\":[{\"name\":\"id\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"ID\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"publicKey\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"JSON\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"publicKeyThumbprint\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"type\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"CreateElectionMutationPayload\",\"description\":\"Autogenerated return type of CreateElectionMutation\",\"fields\":[{\"name\":\"election\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"Election\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"error\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"Election\",\"description\":null,\"fields\":[{\"name\":\"authority\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"id\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"ID\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"logEntries\",\"description\":\"Returns the list of log entries for this election in the bulletin board\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"LogEntry\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"status\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"title\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"trustees\",\"description\":\"Returns the list of trustees for this election\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"ElectionLogEntryAddedPayload\",\"description\":\"Autogenerated return type of ElectionLogEntryAdded\",\"fields\":[{\"name\":\"logEntry\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"LogEntry\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"SCALAR\",\"name\":\"ID\",\"description\":\"Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\\\"VXNlci0xMA==\\\"`) or integer (such as `4`) input value will be accepted as an ID.\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"SCALAR\",\"name\":\"JSON\",\"description\":\"Represents untyped JSON\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"LogEntry\",\"description\":null,\"fields\":[{\"name\":\"chainedHash\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"client\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"election\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Election\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"id\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"ID\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"messageId\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"signedData\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"Mutation\",\"description\":null,\"fields\":[{\"name\":\"createElection\",\"description\":null,\"args\":[{\"name\":\"messageId\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null},{\"name\":\"signedData\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"OBJECT\",\"name\":\"CreateElectionMutationPayload\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"processKeyCeremonyStep\",\"description\":null,\"args\":[{\"name\":\"messageId\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null},{\"name\":\"signedData\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"OBJECT\",\"name\":\"ProcessKeyCeremonyStepMutationPayload\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"vote\",\"description\":null,\"args\":[{\"name\":\"messageId\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null},{\"name\":\"signedData\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"OBJECT\",\"name\":\"VoteMutationPayload\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"PendingMessage\",\"description\":null,\"fields\":[{\"name\":\"client\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"election\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"Election\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"id\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"ID\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"signedData\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"status\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"ProcessKeyCeremonyStepMutationPayload\",\"description\":\"Autogenerated return type of ProcessKeyCeremonyStepMutation\",\"fields\":[{\"name\":\"error\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"pendingMessage\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"PendingMessage\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"Query\",\"description\":null,\"fields\":[{\"name\":\"authorities\",\"description\":\"Returns a list of authorities in the bulletin board\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"election\",\"description\":\"Returns an election given its unique_id\",\"args\":[{\"name\":\"uniqueId\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"OBJECT\",\"name\":\"Election\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"elections\",\"description\":\"Returns a list of elections in the bulletin board\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Election\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"me\",\"description\":\"Returns the information for this bulletin board instance\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"Client\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"pendingMessage\",\"description\":\"Returns the information for a given message\",\"args\":[{\"name\":\"id\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"ID\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"OBJECT\",\"name\":\"PendingMessage\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"SCALAR\",\"name\":\"String\",\"description\":\"Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text.\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"Subscription\",\"description\":null,\"fields\":[{\"name\":\"electionLogEntryAdded\",\"description\":null,\"args\":[{\"name\":\"electionUniqueId\",\"description\":null,\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"defaultValue\":null}],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"ElectionLogEntryAddedPayload\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"VoteMutationPayload\",\"description\":\"Autogenerated return type of VoteMutation\",\"fields\":[{\"name\":\"error\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"pendingMessage\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"PendingMessage\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__Directive\",\"description\":\"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\\n\\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.\",\"fields\":[{\"name\":\"args\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__InputValue\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"description\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"locations\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"ENUM\",\"name\":\"__DirectiveLocation\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"onField\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":true,\"deprecationReason\":\"Use `locations`.\"},{\"name\":\"onFragment\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":true,\"deprecationReason\":\"Use `locations`.\"},{\"name\":\"onOperation\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":true,\"deprecationReason\":\"Use `locations`.\"}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"ENUM\",\"name\":\"__DirectiveLocation\",\"description\":\"A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":[{\"name\":\"QUERY\",\"description\":\"Location adjacent to a query operation.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"MUTATION\",\"description\":\"Location adjacent to a mutation operation.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"SUBSCRIPTION\",\"description\":\"Location adjacent to a subscription operation.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"FIELD\",\"description\":\"Location adjacent to a field.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"FRAGMENT_DEFINITION\",\"description\":\"Location adjacent to a fragment definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"FRAGMENT_SPREAD\",\"description\":\"Location adjacent to a fragment spread.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INLINE_FRAGMENT\",\"description\":\"Location adjacent to an inline fragment.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"SCHEMA\",\"description\":\"Location adjacent to a schema definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"SCALAR\",\"description\":\"Location adjacent to a scalar definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"OBJECT\",\"description\":\"Location adjacent to an object type definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"FIELD_DEFINITION\",\"description\":\"Location adjacent to a field definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"ARGUMENT_DEFINITION\",\"description\":\"Location adjacent to an argument definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INTERFACE\",\"description\":\"Location adjacent to an interface definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"UNION\",\"description\":\"Location adjacent to a union definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"ENUM\",\"description\":\"Location adjacent to an enum definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"ENUM_VALUE\",\"description\":\"Location adjacent to an enum value definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INPUT_OBJECT\",\"description\":\"Location adjacent to an input object type definition.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INPUT_FIELD_DEFINITION\",\"description\":\"Location adjacent to an input object field definition.\",\"isDeprecated\":false,\"deprecationReason\":null}],\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__EnumValue\",\"description\":\"One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.\",\"fields\":[{\"name\":\"deprecationReason\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"description\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"isDeprecated\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__Field\",\"description\":\"Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.\",\"fields\":[{\"name\":\"args\",\"description\":null,\"args\":[{\"name\":\"includeDeprecated\",\"description\":null,\"type\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null},\"defaultValue\":\"false\"}],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__InputValue\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"deprecationReason\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"description\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"isDeprecated\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"type\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__InputValue\",\"description\":\"Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.\",\"fields\":[{\"name\":\"defaultValue\",\"description\":\"A GraphQL-formatted string representing the default value for this input value.\",\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"deprecationReason\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"description\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"isDeprecated\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"type\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__Schema\",\"description\":\"A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.\",\"fields\":[{\"name\":\"directives\",\"description\":\"A list of all directives supported by this server.\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Directive\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"mutationType\",\"description\":\"If this server supports mutation, the type that mutation operations will be rooted at.\",\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"queryType\",\"description\":\"The type that query operations will be rooted at.\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"subscriptionType\",\"description\":\"If this server support subscription, the type that subscription operations will be rooted at.\",\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"types\",\"description\":\"A list of all types supported by this server.\",\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}}}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"description\":\"The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\\n\\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.\",\"fields\":[{\"name\":\"description\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"enumValues\",\"description\":null,\"args\":[{\"name\":\"includeDeprecated\",\"description\":null,\"type\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null},\"defaultValue\":\"false\"}],\"type\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__EnumValue\",\"ofType\":null}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"fields\",\"description\":null,\"args\":[{\"name\":\"includeDeprecated\",\"description\":null,\"type\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null},\"defaultValue\":\"false\"}],\"type\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Field\",\"ofType\":null}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"inputFields\",\"description\":null,\"args\":[{\"name\":\"includeDeprecated\",\"description\":null,\"type\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null},\"defaultValue\":\"false\"}],\"type\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__InputValue\",\"ofType\":null}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"interfaces\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"kind\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"ENUM\",\"name\":\"__TypeKind\",\"ofType\":null}},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"name\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"ofType\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null},\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"possibleTypes\",\"description\":null,\"args\":[],\"type\":{\"kind\":\"LIST\",\"name\":null,\"ofType\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"OBJECT\",\"name\":\"__Type\",\"ofType\":null}}},\"isDeprecated\":false,\"deprecationReason\":null}],\"inputFields\":null,\"interfaces\":[],\"enumValues\":null,\"possibleTypes\":null},{\"kind\":\"ENUM\",\"name\":\"__TypeKind\",\"description\":\"An enum describing what kind of type a given `__Type` is.\",\"fields\":null,\"inputFields\":null,\"interfaces\":null,\"enumValues\":[{\"name\":\"SCALAR\",\"description\":\"Indicates this type is a scalar.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"OBJECT\",\"description\":\"Indicates this type is an object. `fields` and `interfaces` are valid fields.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INTERFACE\",\"description\":\"Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"UNION\",\"description\":\"Indicates this type is a union. `possibleTypes` is a valid field.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"ENUM\",\"description\":\"Indicates this type is an enum. `enumValues` is a valid field.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"INPUT_OBJECT\",\"description\":\"Indicates this type is an input object. `inputFields` is a valid field.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"LIST\",\"description\":\"Indicates this type is a list. `ofType` is a valid field.\",\"isDeprecated\":false,\"deprecationReason\":null},{\"name\":\"NON_NULL\",\"description\":\"Indicates this type is a non-null. `ofType` is a valid field.\",\"isDeprecated\":false,\"deprecationReason\":null}],\"possibleTypes\":null}],\"directives\":[{\"name\":\"include\",\"description\":\"Directs the executor to include this field or fragment only when the `if` argument is true.\",\"locations\":[\"FIELD\",\"FRAGMENT_SPREAD\",\"INLINE_FRAGMENT\"],\"args\":[{\"name\":\"if\",\"description\":\"Included when true.\",\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"defaultValue\":null}]},{\"name\":\"skip\",\"description\":\"Directs the executor to skip this field or fragment when the `if` argument is true.\",\"locations\":[\"FIELD\",\"FRAGMENT_SPREAD\",\"INLINE_FRAGMENT\"],\"args\":[{\"name\":\"if\",\"description\":\"Skipped when true.\",\"type\":{\"kind\":\"NON_NULL\",\"name\":null,\"ofType\":{\"kind\":\"SCALAR\",\"name\":\"Boolean\",\"ofType\":null}},\"defaultValue\":null}]},{\"name\":\"deprecated\",\"description\":\"Marks an element of a GraphQL schema as no longer supported.\",\"locations\":[\"FIELD_DEFINITION\",\"ENUM_VALUE\",\"ARGUMENT_DEFINITION\",\"INPUT_FIELD_DEFINITION\"],\"args\":[{\"name\":\"reason\",\"description\":\"Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).\",\"type\":{\"kind\":\"SCALAR\",\"name\":\"String\",\"ofType\":null},\"defaultValue\":\"\\\"No longer supported\\\"\"}]}]}}}"}},"recorded_at":"Tue, 08 Dec 2020 17:48:11 GMT"},{"request":{"method":"post","uri":"http://localhost:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_210240 {\\n createElection(messageId: \\\"decidim-test-authority.715.create_election+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDc0NDk2OTEsImVsZWN0aW9uX2lkIjoiZGVjaWRpbS10ZXN0LWF1dGhvcml0eS43MTUiLCJtZXNzYWdlX2lkIjoiZGVjaWRpbS10ZXN0LWF1dGhvcml0eS43MTUuY3JlYXRlX2VsZWN0aW9uK2EuZGVjaWRpbS10ZXN0LWF1dGhvcml0eSIsInR5cGUiOiJjcmVhdGVfZWxlY3Rpb24iLCJzY2hlbWUiOnsibmFtZSI6ImR1bW15IiwicGFyYW1ldGVycyI6eyJxdW9ydW0iOjJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJKb2llIFdlc3QgMiIsInB1YmxpY19rZXkiOiJ7XCJrdHlcIjpcIlJTQVwiLFwiblwiOlwiNTlrZnRYbnp3TGs1WUlzR3RaSTdXVXRNMkFaWDRTYWNHbTMtbGlCYmNfbVE1MHlUVUJJeDVLS0xDbjhZcWhFVEpDd1ZKbi1NVzBvSnR3YTBPWmExNWNzcnNnc2Y3TWtYdzRHWkprRmVoMjQ0UTNtNUk3dFBKa2lJZ0NTdkF3dWdPTGFGSUR3THI0NFdSOFhZUGlISzFyWlFfNW9Fd2ZnenFsSWtOR2NmT2pLRzR5OV9tNVgzbXJyRnptOE1DT19kcGMyRmhIcHJ5SmFWMUJra01yemJ1dTdjV2w0ZEpRYWZ5eEw1R2VnZDZjZHYtd2IwX2R5MGFNcTdqYW9YdWMxQTFiaVVrVFFUbzNQT243cFpFU2tZRTBVcnA5cUdNOGZpekNIb0M5OU51QzhqMy1wZ29ibDVNWlVLa0hWTGZwY1lwT05mVDFNZE83TU90WnpjUERmVzVLRHlKOXdGX0laMHFiZTBxVEdkbDhVblJqWDJ6UDh0ZGJ3Q1V6UkpncjhDcjd2aE5RY016SnZpY2VjdGh0YjY2TUtGZXp4RXI2YzVfNWpGRnljZjd6aTlMTV8wOG94N1ZnOGdXWTlWNDgwQy16dEtnbnRnTmRXeWdVYk1iUFAtNmJyNXBWaWl0cHUzYzI4Uk5RMldHN1U2cmtKQW1QYVZMSFY5U2ZTOGZOVEM0MG5ZSUdCaHZxc240R0dIODRaZVhfdnprOHZHaTYxZkF1bWlGdDQxLXhmc2lvdjhMRGR0ZEo0ZFNGVFQ3ZWk0dldxbnA1YlNIWHlIWFRDLWNqem1oSkxMejRYQnJxN2dudU5FdVNwQkpvMFdQZEExeFNHUEJIcFB6OGVyTkJwRzB6YkIxY3h6SFJJQ1h1X1hsNUM1eV92Z2EzVnBFbGZ5cmVhVDROdUJ0V0VcIixcImVcIjpcIkFRQUJcIixcImtpZFwiOlwiNzZmYzFmMjlhYjlkYjVhYTQ0OTI5YmFhNjUwNjFiODgzNTBhYjUyMzM5ZDEwYWEzNTAxMzhiMjhlMzFhZmU4MVwifSJ9LHsibmFtZSI6Ik1lcmlseW4gUHJvc2FjY28gSnIuIDQiLCJwdWJsaWNfa2V5Ijoie1wia3R5XCI6XCJSU0FcIixcIm5cIjpcInhMVEZRU2MtbUFOd3ZDMjNMUmltakdrVC1rcGI3LXk0WXgxZHA4aHk4TlV0Y2hDT1BuOUV1UWhYT1I3aTJnb0Z0VUl1dF9kbFJzVXlQNHVXSG52LVhEYU5KZjdURzkyUjhuS01WR2J0RkZjRWZOUm5yeUVwVDc3TGppT1NHQVlxNk8wSGY5dGNsUTZ4UTdueEZ4azZrYVlHY1V4RWI3MUQ1Uks3RkxBcloxWm1RSk9TQTlHaHU3UmpHZ0phSXAyS2xBa21meHpFMTBEOHhFbFZnc1Nvd0h0TExncEo5UEJGNlMwNk5vc0g5amFNSnhSMG55VF9VdTFweWh4cld1SlFpR2d2dlNpRHh1NlZuMEdtcURsZFV6eFZNSVVJampUWFZnSWRPc3owQ2UzSGZtcWhOeHQtWTZCazM0bzFIdjFFSW9QbUx0NnJpMHRQYTZqcDZ1YU5CRzZMUVJBNmVyd3lzRFlMMjFVdlRZSVA5ZXBIWllmODdsandUM1dCd3lOM1VVa0YyNnVuVGxoTkthclljWGEzUVc0bTZSUWV0alpvQXJ2N25UMjNzTEQzZlYtOUVwckNCVE1iM01fWlduNHpKbnl4LUVHYjNoS1hpckdfS291Tm9hRE92bjBTSFhFMHhNazZ0TkUyM1lvSUY5S215cHNheTN3NGFqNHpFRldsZzVnZlJaT1hPT1ZGS0NQeWdDMVlBMlFxVGE5UUNORkJvVVJhcDJrQW1Xb3V1ZzYtN1A3bHk1eFlvSzUwTWVJZEFwS2RNbkV6VEE4Rmxpd3d2bUwyMFRTTHY4VFFzU3hxYkNxM2lRY0E5cDBFTDgwa05zS0dzS1piMTVKRzZwV01DZlJaRlhtVUtReDRDR0tfRGQyM3U2M1dHN0hkeVRMd2g1WUFTYVFqODJFXCIsXCJlXCI6XCJBUUFCXCIsXCJraWRcIjpcImExZjk5ODA0NjMzNzk1OGIwY2NjM2I1NzQyZWFlNDQxMzUxZjhmODI5ZjY5YTY3NWU2YmE1YzE5NWQ3ZmM1ZmFcIn0ifV0sImRlc2NyaXB0aW9uIjp7Im5hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJdXJlIHZvbHVwdGF0ZXMgaWxsdW0uIDY0IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sInN0YXJ0X2RhdGUiOiIyMDIwLTEyLTA5IDE3OjQ4OjA1IFVUQyIsImVuZF9kYXRlIjoiMjAyMC0xMi0xMSAxNzo0ODowNSBVVEMiLCJjYW5kaWRhdGVzIjpbeyJvYmplY3RfaWQiOiI3NTE1IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJOb24gZGljdGEgZG9sb3IuIDE0NSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MTciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Ik5vbiBxdWlhIG1haW9yZXMuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MTgiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlBlcnNwaWNpYXRpcyBxdWkgb2RpdC4gMTU0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUyMCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVW5kZSBhbGlxdWFtIGxhdWRhbnRpdW0uIDE2MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkxhYm9ydW0gc2l0IHNvbHV0YS4gMjI2IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUzNSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRG9sb3JlcyBhdXQgb21uaXMuIDIzNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MjEiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Ik1vZGkgYXBlcmlhbSB2b2x1cHRhdGVzLiAxNjMiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTIzIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJFeHBsaWNhYm8gbnVtcXVhbSBuaXNpLiAxNjkiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTI4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJEb2xvcmVtcXVlIG1pbnVzIHZvbHVwdGFzLiAyMDIiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTMxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdW9zIGRlYml0aXMgZnVnaWF0LiAyMjMiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTE5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJFYSBzZWQgYWQuIDE1NyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MzYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFkIHV0IHF1YWVyYXQuIDIzOCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Iklwc2FtIGluIHZlbC4gMjQxIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUxMyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTmVxdWUgZXQgaXBzYS4gMTMzIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUyNiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRXQgYWNjdXNhbnRpdW0gY3VscGEuIDE5MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MjkiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkV0IHF1aXMgdm9sdXB0YXRlbS4gMjA4IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUxMiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpIGV4ZXJjaXRhdGlvbmVtIGF1dGVtLiAxMzAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTE2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXRlbSBlb3MgdmVyby4gMTQ4IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUxMSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRWxpZ2VuZGkgbm9uIHJlY3VzYW5kYWUuIDEyNyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkNvbnNlcXV1bnR1ciBhdXQgZXhwbGljYWJvLiAxNDIiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NTIyIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJEb2xvcmVtIHNlZCBleGNlcHR1cmkuIDE2NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1YXMgZXggYXJjaGl0ZWN0by4gMTg0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUzMCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUGxhY2VhdCByZXBlbGxlbmR1cyBwb3Jyby4gMjIwIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUyNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSXBzdW0gZXJyb3Igdm9sdXB0YXRlbS4gMTk2IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzUzMyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVmVyaXRhdGlzIG9tbmlzIHF1aWEuIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MjQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkN1bSBkb2xvcmVtIGVuaW0uIDE3OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1MzQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlBsYWNlYXQgaXVzdG8gY3VwaWRpdGF0ZS4gMjMyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19XSwiY29udGVzdHMiOlt7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6IjEyMzMiLCJzZXF1ZW5jZV9vcmRlciI6MSwidm90ZV92YXJpYXRpb24iOiJvbmVfb2ZfbSIsIm5hbWUiOiJOb3N0cnVtIHF1aSBhbGlxdWFtLiAxMjEiLCJudW1iZXJfZWxlY3RlZCI6Mywidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiTm9zdHJ1bSBxdWkgYWxpcXVhbS4gMTIxIiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkxhYm9yaW9zYW0gYSBleGNlcHR1cmkuIDEyNDwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6Ijc1MTMiLCJzZXF1ZW5jZV9vcmRlciI6NCwiY2FuZGlkYXRlX2lkIjoiNzUxMyJ9LHsib2JqZWN0X2lkIjoiNzUxMiIsInNlcXVlbmNlX29yZGVyIjo2LCJjYW5kaWRhdGVfaWQiOiI3NTEyIn0seyJvYmplY3RfaWQiOiI3NTExIiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6Ijc1MTEifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiMTIzNSIsInNlcXVlbmNlX29yZGVyIjoyLCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJRdWkgb2RpbyBldC4gMTcyIiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBvZGlvIGV0LiAxNzIiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-QXBlcmlhbSBxdWlidXNkYW0gYWNjdXNhbXVzLiAxNzU8L3A-IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zZWxlY3Rpb25zIjpbeyJvYmplY3RfaWQiOiI3NTI4Iiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6Ijc1MjgifSx7Im9iamVjdF9pZCI6Ijc1MjYiLCJzZXF1ZW5jZV9vcmRlciI6NCwiY2FuZGlkYXRlX2lkIjoiNzUyNiJ9LHsib2JqZWN0X2lkIjoiNzUyOSIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI3NTI5In0seyJvYmplY3RfaWQiOiI3NTI1Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6Ijc1MjUifSx7Im9iamVjdF9pZCI6Ijc1MjciLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiNzUyNyJ9LHsib2JqZWN0X2lkIjoiNzUyNCIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI3NTI0In1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6IjEyMzQiLCJzZXF1ZW5jZV9vcmRlciI6Mywidm90ZV92YXJpYXRpb24iOiJuX29mX20iLCJuYW1lIjoiRXQgZGljdGEgZGlnbmlzc2ltb3MuIDEzNiIsIm51bWJlcl9lbGVjdGVkIjoxMCwidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiRXQgZGljdGEgZGlnbmlzc2ltb3MuIDEzNiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc3VidGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiI8cD5MYWJvcnVtIHRlbXBvcmEgZmFjaWxpcy4gMTM5PC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNzUxNSIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI3NTE1In0seyJvYmplY3RfaWQiOiI3NTE3Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6Ijc1MTcifSx7Im9iamVjdF9pZCI6Ijc1MTgiLCJzZXF1ZW5jZV9vcmRlciI6MCwiY2FuZGlkYXRlX2lkIjoiNzUxOCJ9LHsib2JqZWN0X2lkIjoiNzUyMCIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI3NTIwIn0seyJvYmplY3RfaWQiOiI3NTIxIiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6Ijc1MjEifSx7Im9iamVjdF9pZCI6Ijc1MjMiLCJzZXF1ZW5jZV9vcmRlciI6MSwiY2FuZGlkYXRlX2lkIjoiNzUyMyJ9LHsib2JqZWN0X2lkIjoiNzUxOSIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI3NTE5In0seyJvYmplY3RfaWQiOiI3NTE2Iiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6Ijc1MTYifSx7Im9iamVjdF9pZCI6Ijc1MTQiLCJzZXF1ZW5jZV9vcmRlciI6NywiY2FuZGlkYXRlX2lkIjoiNzUxNCJ9LHsib2JqZWN0X2lkIjoiNzUyMiIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI3NTIyIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6IjEyMzYiLCJzZXF1ZW5jZV9vcmRlciI6NCwidm90ZV92YXJpYXRpb24iOiJuX29mX20iLCJuYW1lIjoiRmFjaWxpcyBjb21tb2RpIGN1bS4gMjE0IiwibnVtYmVyX2VsZWN0ZWQiOjgsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2lsaXMgY29tbW9kaSBjdW0uIDIxNCIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc3VidGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiI8cD5FYSBwb3NzaW11cyBwcmFlc2VudGl1bS4gMjE3PC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNzUzMiIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI3NTMyIn0seyJvYmplY3RfaWQiOiI3NTM1Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6Ijc1MzUifSx7Im9iamVjdF9pZCI6Ijc1MzEiLCJzZXF1ZW5jZV9vcmRlciI6MSwiY2FuZGlkYXRlX2lkIjoiNzUzMSJ9LHsib2JqZWN0X2lkIjoiNzUzNiIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI3NTM2In0seyJvYmplY3RfaWQiOiI3NTM3Iiwic2VxdWVuY2Vfb3JkZXIiOjMsImNhbmRpZGF0ZV9pZCI6Ijc1MzcifSx7Im9iamVjdF9pZCI6Ijc1MzAiLCJzZXF1ZW5jZV9vcmRlciI6NywiY2FuZGlkYXRlX2lkIjoiNzUzMCJ9LHsib2JqZWN0X2lkIjoiNzUzMyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI3NTMzIn0seyJvYmplY3RfaWQiOiI3NTM0Iiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6Ijc1MzQifV19XX19.df445_2huLo3TkOZvSx60f08DQNhZKIxminr9Xm7l_9pmqcErIxXma0-r1aR4CXdP3m7fLwb7a7lzEtNjPB7h4UDx7BqDqNFriXxtFprkz83ygfmyHUA-LqYZ1UlcOGVv6wD2vjWzptHtOIcqCrC7fwSyk8BQO649aGdwrtUPzS-GXWbZy9J1GvwNyuQry3Tulr9wijPUFO7eZK1hgtoEwwn-955XQFrzBmialXnKchqrsxdAZTxl4CGpPYpGqeeu6LFd5DxgQloVU2QDTNwlu5r0XO4kQg-7A4W-5uYN8juRoUKGO59ZpWc6UU3ySHd8O5bwSQ_svEpGLbgi5iTNCmlwaxUTzdDGn-vh1_R0XpPf_f5LrKw3TwP_xdd5W-gpW695dY1Ddp5i7hnLak6jK8nE2lvr7DjtrrjIh5hyUr2oZ8pS1Smo1Mqnrglj_AkoeAaB_Ud6GP_SGCm8SQTh34H13PJX0lWjQ71gPJqi0wXILpdX0_ihMYTZiLpku0fEyiotPpmYnJWLakk_hDGkRkDZsVNNQfGLqLwbg8MbmWkg8XA2DmbAYVE7TGGQLsvnaL9ynrlMsoE9BwWmZM01R0AWukWu5QzyccGejTMZ0NUGlK7ncXRWbL3kH2hup77l_2XmK1OhC2gKv5gZMHQewxCxNY71LfWkL1ikWCjpJo\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_210240\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"254cb984da3479e99fd26dc4dc425048\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["ae711b70-7aca-4779-a0b7-1fb923ae19e6"],"X-Runtime":["15.239838"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"createElection\":{\"election\":{\"status\":\"key_ceremony\"},\"error\":null}}}"}},"recorded_at":"Tue, 08 Dec 2020 17:48:27 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_212720 {\\n createElection(messageId: \\\"decidim-test-authority.3.create_election+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2MzA4NzYsImVsZWN0aW9uX2lkIjoiZGVjaWRpbS10ZXN0LWF1dGhvcml0eS4zIiwibWVzc2FnZV9pZCI6ImRlY2lkaW0tdGVzdC1hdXRob3JpdHkuMy5jcmVhdGVfZWxlY3Rpb24rYS5kZWNpZGltLXRlc3QtYXV0aG9yaXR5IiwidHlwZSI6ImNyZWF0ZV9lbGVjdGlvbiIsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJHZXJhbGRvIEhhZ2VuZXMiLCJwdWJsaWNfa2V5Ijoie1wia3R5XCI6XCJSU0FcIixcIm5cIjpcIm1mdmgzRnlyWHpiTUo2OWhOTU9ZanBwNXd6TTAzZVVQY0pBOTdhNGhhRXZqZkNRaXA1R2RyUGJWMHpUYXZmZkQ4b1FvZExnSnRoSWRadlBSc0lXdUxBaE5oOW9YR1czOWREOHlsaGZXRXZBRGY5b0Z0b0JGaFpuRmVJaW5OMkVGZ002cHBJZUdpZHRlTWNoSXVSTzBLSHlBT1N4Y1pDZk1FRHQ0el9aNWVLQ0NBT05UUGhlYkNxbE1uZ29WaU1vX25NdFk4YTEzdTdINjBEMVFFNjRYaEVTMmRxZnBHTjVhWmtka2tQRDg2VEluSHFBaGJCMmV5Ynk0ZHhXSS1SbU5mTW5aakJLSGFDYzlDRGgwYkdPdEJ5VW5JaC1hUUNNUkZyanBPZ21IbDYyQUNDa3lKQllBamZOM1NFdUVVOFFYczMxZWE2b1QtVXZ2eU1QajZJVG9hX1JkX1Rscl9zOXdZLWZGTVN6cDFlWURDV25feHJuZXlIVXRqaW91OXZ4M0duajlrTVcycEFWTWNmLUNyQ0pKM09iUUdZdE0tdThaYWF5cXI2ZE5mQlBsLU9sckhQYlBJSlV4UDlHeHJwOE56Y3kyZmNoX1VtUVVrVkZYM3MyZHIzbUdlVGtwbjdFMFFHd1RqTURYRnFGTWFJM0ZhRE1jWFltVjFSLXlsWEhVU2tOa0xQRlpmR3JQTFdYUGZiS0RsOE5sV2R1RlpRbnF6M19veUVnX2VMckxETVpzYWVQWlhoemJpcVZvX2hURGwzellySGlDVndBN3M5T1pvOVFhZE9jbmp0TnFkbGhvMktJRk05bTZ0d1ZUWlkzWjVjNlRiN1JsZklFTU5nLUFENkl0Z0NsNkRBNEc4dnVUZTFsRVBMZlFqSDVway1CWS0yY2haWXZyeFRrXCIsXCJlXCI6XCJBUUFCXCIsXCJraWRcIjpcIjAyYWRlZGM0MTJmNDllZGI0NjlmMDM0YTA2ZWM0ZDA2MDMxY2M4ZmRmNGNmMWYwNzI1N2VlYmQ2ZDE3NmY3MGVcIn0ifSx7Im5hbWUiOiJEb3ZpZSBTaGFuYWhhbiBJIiwicHVibGljX2tleSI6IntcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCIwMVBZem9aOGpXVjBlN21zTmh6MXlWVndTeDBvYWxtNndhQWthLU5oN3N6Vk5pdkh1N2toTzJCTW9VQS1qYVlfdTJzVjFSYXRreVVnV2ZkVVFldUJZQUg1UG5SYUktNndYcEx3ZXJaeTVyRkdKVS1ZS0w5Zk5hT1BENVhuNkFHaEdpaFV6ZkM0STBZcDQ1Mk1VdVpnX3N1a3d4Mkw4MTVFaTJaTFNxRDQteTQzNGkydmZBT3ZEMHBDVmNuS2xQTVpZbHE4QTBxd0tvVU9raDNJdjFrbjJRZEdSdUxrUHhBVk5VQTA0T2dJTi1jRVRHaHh0MFNoQjZiT0pWbWNRVS1pMnVwQW5YRW80ZEpYQWY0SUJJVENhUEoxTTdVcEV0MG9WOFlBY00teEVwMEhkd1VyVUw2cE44TEMzSWFvN1lISlRzTUlxc2JXT3FkMmcxcVZkSlY0NmxmeGJyLWYtb0FJMDByc2VkU0JRSFJZV3RXNHE3STBud0FxN3BZaGExSzRKek9tdmtjVnNFczJYVk9VenFEOG00NHVBY0JoX0pmVmFVLWZKYlJjLUNDZm5iSnhfd2syMFFCNG1FNHpXdW43bHR5em10LVBRbHloMlBKQTBLWUJWQTM5cFIwd0pNUm9uSmNiSVE4RnBmU0l3dlNrVHlSYnNpQVRPLXdycDctemY1d2FLSzF3SFJoUGhRbkFXSUVocnk0WEVudXNuZFQ2djNKaDdDTlNfVS1oaXdsLTRBVUpOcUhiVldkN0dXVGxWRXNORkdVTWZNTi1Oc29ELXhxXzdLbDNrSTN1VC12Q3ZuM0FMb1RXbmRJeF8tdGlXM3VSS18zLVk5cjZJLTFKTmMwZ0pIaHRGRE5nSDdvQjJZWXFDeThYRmgxcGVFSEFUbnMyYVMwQTktc1wiLFwiZVwiOlwiQVFBQlwiLFwia2lkXCI6XCI5NWFkM2I5NTA1YjgyYzhiYmNkYTY3ZDYwZDYxNDBlMWJmNzM3NmY2NWFlZDQ5Njg0ZWUzNDg0NDIzM2UyZTM2XCJ9In1dLCJkZXNjcmlwdGlvbiI6eyJuYW1lIjp7InRleHQiOlt7InZhbHVlIjoiQ3VtIHJlcGVsbGF0IGFkaXBpc2NpLiA2NCIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMC0xMi0yMyAwOTo1NDozMyBVVEMiLCJlbmRfZGF0ZSI6IjIwMjAtMTItMjUgMDk6NTQ6MzMgVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNjYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b3MgcXVpYSBhdXQuIDE2NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjY5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJVdCBzaW50IG5paGlsLiAxODQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUmVwZWxsYXQgdGVtcG9yZSBlYS4gMjI2IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNjEiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlRlbXBvcmUgbW9sZXN0aWFlIGlwc2EuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjYyIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWkgYWQgZXQuIDE1NCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjY3IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJbGx1bSBxdWFzaSBhbmltaS4gMTY5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlBvcnJvIHJlaWNpZW5kaXMgbmVzY2l1bnQuIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjgxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaW1pbGlxdWUgbW9sbGl0aWEgbmloaWwuIDI0MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjU3IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBY2N1c2FudGl1bSBleHBsaWNhYm8gYXV0LiAxMzMiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI2MyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSXBzYSBxdWlidXNkYW0gcXVvcy4gMTU3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkR1Y2ltdXMgbmlzaSBzZWQuIDE2MyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc1IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBcGVyaWFtIG1hZ25pIHN1bnQuIDIyMyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjYwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJZCBuYW0gb21uaXMuIDE0OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjcxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlkZW0gdml0YWUgZGVzZXJ1bnQuIDE5NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjU2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJZCBxdWlzIGRpc3RpbmN0aW8uIDEzMCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjY0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJMYXVkYW50aXVtIGxpYmVybyBjb25zZXF1YXR1ci4gMTYwIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Ik5paGlsIHBsYWNlYXQgZGlnbmlzc2ltb3MuIDIwOCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6Ijc4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBZGlwaXNjaSBleHBsaWNhYm8gZGVsZW5pdGkuIDIzMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjcwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJFeHBlZGl0YSB1dCB1dC4gMTkwIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiODAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1byBuZWNlc3NpdGF0aWJ1cyBtb2xlc3RpYWUuIDIzOCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjU1IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaW50IGN1bSB1bmRlLiAxMjciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1OCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiU2VkIHZvbHVwdGFzIGV0LiAxNDIiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1OSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVm9sdXB0YXRlbSBtYWduaSBlb3MuIDE0NSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjY4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXQgYXNzdW1lbmRhIHNpdC4gMTc4IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlRlbXBvcmUgcXVpIG9tbmlzLiAyMDIiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3NCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSGFydW0gZXN0IGRlbGVjdHVzLiAyMjAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI3OSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiRnVnaWF0IHF1aXMgY3VtcXVlLiAyMzUiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiOSIsInNlcXVlbmNlX29yZGVyIjoxLCJ2b3RlX3ZhcmlhdGlvbiI6Im9uZV9vZl9tIiwibmFtZSI6IkluIHF1b3MgZXQuIDEyMSIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJJbiBxdW9zIGV0LiAxMjEiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RG9sb3JlbSBzZWQgaWxsby4gMTI0PC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTciLCJzZXF1ZW5jZV9vcmRlciI6NCwiY2FuZGlkYXRlX2lkIjoiNTcifSx7Im9iamVjdF9pZCI6IjU2Iiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjU2In0seyJvYmplY3RfaWQiOiI1NSIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI1NSJ9XX0seyJAdHlwZSI6IkNhbmRpZGF0ZUNvbnRlc3QiLCJvYmplY3RfaWQiOiIxMSIsInNlcXVlbmNlX29yZGVyIjoyLCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJJcHNhbSBxdWkgaW4uIDE3MiIsIm51bWJlcl9lbGVjdGVkIjo2LCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJJcHNhbSBxdWkgaW4uIDE3MiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc3VidGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiI8cD5RdW9kIG5vc3RydW0gbWludXMuIDE3NTwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjY5Iiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjY5In0seyJvYmplY3RfaWQiOiI3MSIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI3MSJ9LHsib2JqZWN0X2lkIjoiNzMiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNzMifSx7Im9iamVjdF9pZCI6IjcwIiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjcwIn0seyJvYmplY3RfaWQiOiI2OCIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI2OCJ9LHsib2JqZWN0X2lkIjoiNzIiLCJzZXF1ZW5jZV9vcmRlciI6OSwiY2FuZGlkYXRlX2lkIjoiNzIifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiMTAiLCJzZXF1ZW5jZV9vcmRlciI6Mywidm90ZV92YXJpYXRpb24iOiJuX29mX20iLCJuYW1lIjoiVm9sdXB0YXRlbSBtYWduaSBmYWNpbGlzLiAxMzYiLCJudW1iZXJfZWxlY3RlZCI6MTAsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZvbHVwdGF0ZW0gbWFnbmkgZmFjaWxpcy4gMTM2IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPlZlbCB2b2x1cHRhdGlidXMgc2VkLiAxMzk8L3A-IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zZWxlY3Rpb25zIjpbeyJvYmplY3RfaWQiOiI2NiIsInNlcXVlbmNlX29yZGVyIjoxLCJjYW5kaWRhdGVfaWQiOiI2NiJ9LHsib2JqZWN0X2lkIjoiNjEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNjEifSx7Im9iamVjdF9pZCI6IjYyIiwic2VxdWVuY2Vfb3JkZXIiOjIsImNhbmRpZGF0ZV9pZCI6IjYyIn0seyJvYmplY3RfaWQiOiI2NyIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI2NyJ9LHsib2JqZWN0X2lkIjoiNjMiLCJzZXF1ZW5jZV9vcmRlciI6NCwiY2FuZGlkYXRlX2lkIjoiNjMifSx7Im9iamVjdF9pZCI6IjY1Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjY1In0seyJvYmplY3RfaWQiOiI2MCIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI2MCJ9LHsib2JqZWN0X2lkIjoiNjQiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNjQifSx7Im9iamVjdF9pZCI6IjU4Iiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6IjU4In0seyJvYmplY3RfaWQiOiI1OSIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI1OSJ9XX0seyJAdHlwZSI6IkNhbmRpZGF0ZUNvbnRlc3QiLCJvYmplY3RfaWQiOiIxMiIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJBYiBkZWxlY3R1cyBpcHNhbS4gMjE0IiwibnVtYmVyX2VsZWN0ZWQiOjgsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFiIGRlbGVjdHVzIGlwc2FtLiAyMTQiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-UXVpYSBwb3NzaW11cyBzZWQuIDIxNzwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6Ijc2Iiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6Ijc2In0seyJvYmplY3RfaWQiOiI3NyIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI3NyJ9LHsib2JqZWN0X2lkIjoiODEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiODEifSx7Im9iamVjdF9pZCI6Ijc1Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6Ijc1In0seyJvYmplY3RfaWQiOiI3OCIsInNlcXVlbmNlX29yZGVyIjo2LCJjYW5kaWRhdGVfaWQiOiI3OCJ9LHsib2JqZWN0X2lkIjoiODAiLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiODAifSx7Im9iamVjdF9pZCI6Ijc0Iiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6Ijc0In0seyJvYmplY3RfaWQiOiI3OSIsInNlcXVlbmNlX29yZGVyIjo5LCJjYW5kaWRhdGVfaWQiOiI3OSJ9XX1dfX0.FOFYJf1pyRLgMdPZyHH72qg9PurTwiX4qOrn7ibjY4-f9daNjeXb7jaF354hgtoTkp5Z1ZppG1ngsWS6TMA0xiSeHAq_2ucH_PmfkQIx6Nl0E-k8hkmmN9XEbJMWEIORkWEH15lzMG5kIDvyENudsxNS6kxbQpt8_R7XPEvQPatOuJcmLpWusS85b9Ch7xYaARtgFJBZASPdLVpincf3flhm_zvL-6PslC_l4G0mdy72rWH-hELD5nCTLHCP4D1Kq709HFCKia9nJANBAo8AtlaDJMXBJbhtIOWoKDXhvIu2hMTVNvYrM8HChrtxR-j40eQUA64NhjLmu9kb0fNLmOeD1StbC57sBwEhxsE8P3kXn9jWtZBPmu3zyGbUUpR8bJvr3aotDPm68nh0UiElNkEP-Fmkh3pVjLS1PvrRlUy4qxGx9pLYZ5Oo6wUJyGdo562gtAtADzUbbcrUqf2xtZNpL5ddxVajnXwmpIob34oUN_i-y9WGIzgXRsAFlU_vTrwKT6ATQoJZafx3vJUmn5ox4UNYGu-zXxA6KPneoqyDG_ZwyP0vT2OLk2LjwrHTR1d4zv7CB9FHfdr6cJUuSzGjT6V1duRlF6de_qYwo0-3yc5qy8gaK2489cUC1_Otxdy8uMDuJsQv5GgGzy_ihDsj5LCZTua89lQQTX5H2-Q\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_212720\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"254cb984da3479e99fd26dc4dc425048\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["aa0ef2df-7fc0-44ad-9984-b462ede39c54"],"X-Runtime":["0.934320"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"createElection\":{\"election\":{\"status\":\"key_ceremony\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 09:54:37 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/Key_ceremony/key_ceremony_process/when_performing_the_key_ceremony/generates_backup_keys_restores_them_and_creates_election_keys.json b/decidim-elections/spec/cassettes/Key_ceremony/key_ceremony_process/when_performing_the_key_ceremony/generates_backup_keys_restores_them_and_creates_election_keys.json new file mode 100644 index 0000000000000..7f547b0f5d32b --- /dev/null +++ b/decidim-elections/spec/cassettes/Key_ceremony/key_ceremony_process/when_performing_the_key_ceremony/generates_backup_keys_restores_them_and_creates_election_keys.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_214060 {\\n createElection(messageId: \\\"decidim-test-authority.42.create_election+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MTA1NjA4MjQsInNjaGVtZSI6eyJuYW1lIjoiZHVtbXkiLCJwYXJhbWV0ZXJzIjp7InF1b3J1bSI6Mn19LCJhdXRob3JpdHkiOnsibmFtZSI6IkRlY2lkaW0gVGVzdCBBdXRob3JpdHkiLCJwdWJsaWNfa2V5Ijp7Imt0eSI6IlJTQSIsIm4iOiJwTmdNdDhsblBERDNUbFdZR2hSaVYxb1prUFFtbkxkaVV6d3liXy0zNXFLRDlrLUhVODZ4bzB1U2dvT1VXa0J0bnZGc2NxOHpORFBBR0FsWlZva2FOX3o5a3NaYmxTY2UwTEVsOGxKYTNJQ2dnaGc3ZTh2Z183THo1ZHlIU1EzUENMZ2VueUZHY0w0MDFhZ2xEZGUxWG80dWpkejMzTGtsYzRVOXpveW9MVUkyX3ZpWW1OT1U2bjVNbjBzSmQzMEZlSUNNckxEMmdYNDZwR2UzTUd1ZzZncm9UOUV2cEtjZE9vSkhLb081eUdTVmFlWTUtQm8zZ25ndmxnamxTMm1md2pDdEY0Tll3SVFTZDJhbC1wNEJLbnVZQVZLUlNncjhyWW5uamhXZko0R3NDYXFpeVhOaTVOUFlSVjZnbF9jeF8xalVjQTFyUkpxUVIzMkk4YzhRYkFYbTVxTk80VVJjZGFLeXM5dE5jVmdYQkwxRnNTZGJyTFZWRldlbjF0ZldOZkhtLThCamlXQ1dENzktdWs1Z0kwU2pDOXRXdlR6VnZzd1dYSTV3ZU5xcVZYcXBEeWRyNDZBc0hFMnNHNDBIUkNSM1VGM0x1cFQtSHdYVGNZY09acjVkSkNsSklzVTNIcnZ5NHdMc3N1YjY5WVNOUjFKeG4tS1gydlVjMDZ4WThDTkl1U01wZnVmRXE1Y1pvcEw2TzJsMXBSc1cxRlFuRjNzMDc4X1k5TWFRMWdQeUJvMElpcExCVlVqNUlqRUlmUHVpRWs0anhraVVZRGVxemY3YkF2U0Zja3A5NHlMa1JXVHNfcEVaczdiX29nd1JHNldNSGp0Y2FOWWU0Q3VmaEltOWVra0tEZUFXT1BSVEhmS05tb2hSQmgwOVh1dlNqcXJ4NVo3cnFiOCIsImUiOiJBUUFCIiwia2lkIjoiYjhkYmExNDU5ZGY5NTZkNjAxMDc2OTBjMzRmYTQ5MGRiNjgxZWFjNGY3M2ZmYWY2ZTQwNTU3MjhjMDJkZGM4ZSJ9fSwidHJ1c3RlZXMiOlt7Im5hbWUiOiJUcnVzdGVlIDEiLCJwdWJsaWNfa2V5Ijoie1wiYWxnXCI6XCJSUzI1NlwiLFwiZVwiOlwiQVFBQlwiLFwia3R5XCI6XCJSU0FcIixcIm5cIjpcInJIcE5WT3ZwTHJkTU1BV3NKaDQyVUtubjVzQVVSaEI3QS1zRVFlMGhROGdrNWIzdFZ1ZmFiQUpJc2ZFQXJzanl0OTFVSVhnbVNueUdkOHRia0VyczUzRlljTFhnWndERUoxTk5jQmRIaV9pR0pVTEYxSU9LaHhXSEpTMVFLamtQb1NhbUNUZW9ROGhXcTBtZFdIRjFnc2lPdFJqSWZ0LVJYN0FtMlpxazUzQ2VQRTJUVlJxbkh1RW1NUUhRLWxjUDJQYmUxN2NUNGk2UG95REl1YW5iYWJlbkRrdE56R1hHN0JmRDRPN3poTVdCMGRFMVNRWlNSMDlkYW5Dem81M3hrNE5sb1Y5eENSOVZjU25HdXMtQW5LSVpRWkZYaTBRcUlLNmk4VkZOdUE5UFlDQ2ZsZVVXQ0F5SFJRTWtkOVhTemJUczhPZVdBdmc0UmNiOFd2NUdrTDhhY2F6eTdHMGRpaUF2b3RJcjA1OENOb0ZHcXZ0dUtueGlsUDJhcGFZd0NCVGEtU1FwNERxQ2lZY0F0UGpfV2VSVGJhcDZXeURjZnl4TDVMeVVReXNWbUduNGZCNG5MVm9EV3g0elZtVncwU2JHUVQ4SzRGbWtJaGp0ZERtRUxQYWllbnRHOFRlcDRJQmpQdEZTYS1STUxBdTQ1akhkbHNYQlpqbXJiZVRid1RCV1Y0YVNLcDZUMmxjRU9jLW14cm9ycldnVEdZeTJNdnBKSExhbmVleGVmRU1WQjhpWEpKWDJMNEpBcUtDd3E3VjhDd3VtMFpzVFdhS0xzR1JOcVBlQXdheUFKZVh1aVQ1aERzM2ktY3V5dXFyYzJnbVd4dTVGRUdaSEpRV0dfTUxuWUFUWV9ubElEcS1vdnVHR0phLWxzenU2cHlWdkNLcnlDNzBta3NNXCJ9In0seyJuYW1lIjoiVHJ1c3RlZSAyIiwicHVibGljX2tleSI6IntcImFsZ1wiOlwiUlMyNTZcIixcImVcIjpcIkFRQUJcIixcImt0eVwiOlwiUlNBXCIsXCJuXCI6XCJtWVc0cHk1NWVIVnpsQU9MZVdTbGtYLVV6RmNXbVBaTmd0MzhZZ2ZEc3JXc01pRjk5TnhlUzFoSEJCQkFJTVlWajREZHJhdW54d3hpYkZaSm00QjdKZkxlZE8wMnBHSFhXcHgtVlVTd1JVN1RLQk84S0djTEl1eUlzTEFuNFZabWNVQ2ZPRDR6NEQzbU9QWEpGcm15X2RiNEVRSmdRbmtSUWRvcDlOYi16RXhqWWR3OUlPblNGWWRZTEo2ay1nNE9udTQza1VJVkRqQkJ3QmxOSElrci16cEFwUFdoQUJVcFNWX1Z5NldBZGt1M2l4NE15RlFMUTQ4bGpKcDRLVktydzg1a20zeDVrMXA3eVhwUlhhZUFVd2RsMXVjWlVIZ2ViMjNFRC1xMmd3QVNBYnJCTE9NbHJyUnJleE9iUlhsSGQ3Z2tRUzRmd3ZMdGVoRU5ZMEVlcGFTNlI5WkRHRDVsOEFKT2xTM2pHQ0JmUWRwTDlhSlQwNVpxbUI4VmkwRmo4UGdEa3pzZWxxcDllU3dVQmI4MmJwOHBhZFg5TnNaUGFva1hMdEpsczhhY1NPUHBCRHdtRVBBaVVNTDdMV1V3aVJ5bGltSzRDVkROeHVsTkduQnVQbFlsNWI0dlpRSHRwakJFRmZLVVp4SThxWTFZMnFwRWVoYVlEMG9YYTY0eXIySGk1SVZkbENPUzg4aHFQc2tQdHRvZTI4Y202WVZZZmxKUDlidjRzbHFldTFjYWZEU0N4WDdBNFBYODN3RHBXNWd5V0MzY1hBUFRYaDJQbHhsbm4zcDlLVGFlNl9ZUGtJOElkMjZ6a202MUxwS2ktQ2tRQ3ZvMmZIdmZaSTIzSmlTajNZOUtZWlIyNVltOE85YWFOQmRoZ3ZOS2hCOFZGbGl6S2V0M1ZVOFwifSJ9XSwiZGVzY3JpcHRpb24iOnsibmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkxhYm9yaW9zYW0gdmVsaXQgdm9sdXB0YXRpYnVzLiA1OCIsImxhbmd1YWdlIjoiZW4ifV19LCJzdGFydF9kYXRlIjoiMjAyMS0wMS0xNCAxODowMDoyMCBVVEMiLCJlbmRfZGF0ZSI6IjIwMjEtMDEtMTYgMTg6MDA6MjAgVVRDIiwiY2FuZGlkYXRlcyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiSGFydW0gY3VwaWRpdGF0ZSB0ZW1wb3JlLiAxNDUiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI0IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJWb2x1cHRhdGUgbmVtbyBldW0uIDE2MyIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkRvbG9yZXMgdm9sdXB0YXR1bSBsYWJvcnVtLiAyMTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJSZXByZWhlbmRlcml0IGRlbGVjdHVzIGV0LiAyMTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIxIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTZXF1aSBwYXJpYXR1ciBpbGxvLiAxNTQiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjMwIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJRdWlhIHN1bnQgdXQuIDIwMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFtZXQgYXJjaGl0ZWN0byBhc3N1bWVuZGEuIDIyNiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzYiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNhZXBlIHZvbHVwdGF0aWJ1cyBuYW0uIDIyOSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b3MgY3VtIHNpdC4gMTI3IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNiIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTmFtIGl0YXF1ZSBlbmltLiAxNzgiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjE2IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJJbmNpZHVudCBhY2N1c2FtdXMgZG9sb3JlbXF1ZS4gMTM5IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxOSIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiVXQgdXQgdml0YWUuIDE0OCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkZhY2lsaXMgdXQgcXVpLiAxMzYiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjIzIiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJDb25zZWN0ZXR1ciBxdWlhIGVhcnVtLiAxNjAiLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI5IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJTaXQgYXV0IHF1aXMuIDE5NiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzQiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkFzcGVyaW9yZXMgZnVnYSBldC4gMjIzIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIzOCIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiTm9uIHF1YWUgb21uaXMuIDIzNSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1aSBsYWJvcmlvc2FtIGRlbGVjdHVzLiAxNTciLCJsYW5ndWFnZSI6ImVuIn1dfX0seyJvYmplY3RfaWQiOiI1MjI4IiwiYmFsbG90X25hbWUiOnsidGV4dCI6W3sidmFsdWUiOiJBdXRlbSBwb3JybyBzaXQuIDE5MCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlNpbnQgZGlzdGluY3RpbyBtYWduYW0uIDEyNCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjUiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlJlcnVtIGV4cGVkaXRhIGRvbG9yZS4gMTcyIiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIyNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiUXVpcyBlbmltIGludmVudG9yZS4gMTg0IiwibGFuZ3VhZ2UiOiJlbiJ9XX19LHsib2JqZWN0X2lkIjoiNTIxNyIsImJhbGxvdF9uYW1lIjp7InRleHQiOlt7InZhbHVlIjoiT21uaXMgbWFnbmFtIHZvbHVwdGF0ZW0uIDE0MiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMjAiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1b2Qgdml0YWUgdXQuIDE1MSIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzMiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IkJsYW5kaXRpaXMgZGljdGEgZXQuIDIyMCIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMzciLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6Iklwc2EgZG9sb3JlcyBpbnZlbnRvcmUuIDIzMiIsImxhbmd1YWdlIjoiZW4ifV19fSx7Im9iamVjdF9pZCI6IjUyMTIiLCJiYWxsb3RfbmFtZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlF1YXNpIHZvbHVwdGF0ZW0gZmFjZXJlLiAxMjEiLCJsYW5ndWFnZSI6ImVuIn1dfX1dLCJjb250ZXN0cyI6W3siQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzczIiwic2VxdWVuY2Vfb3JkZXIiOjEsInZvdGVfdmFyaWF0aW9uIjoib25lX29mX20iLCJuYW1lIjoiUGFyaWF0dXIgZWFxdWUgdXQuIDExNSIsIm51bWJlcl9lbGVjdGVkIjozLCJ2b3Rlc19hbGxvd2VkIjoxLCJiYWxsb3RfdGl0bGUiOnsidGV4dCI6W3sidmFsdWUiOiJQYXJpYXR1ciBlYXF1ZSB1dC4gMTE1IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPlN1c2NpcGl0IHF1byBlb3MuIDExODwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMTQiLCJzZXF1ZW5jZV9vcmRlciI6MywiY2FuZGlkYXRlX2lkIjoiNTIxNCJ9LHsib2JqZWN0X2lkIjoiNTIxMyIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjEzIn0seyJvYmplY3RfaWQiOiI1MjEyIiwic2VxdWVuY2Vfb3JkZXIiOjksImNhbmRpZGF0ZV9pZCI6IjUyMTIifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc1Iiwic2VxdWVuY2Vfb3JkZXIiOjIsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibnVtYmVyX2VsZWN0ZWQiOjYsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlByb3ZpZGVudCBleHBsaWNhYm8gbWFpb3Jlcy4gMTY2IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkF1dCBhdXRlbSBhY2N1c2FtdXMuIDE2OTwvcD4iLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3NlbGVjdGlvbnMiOlt7Im9iamVjdF9pZCI6IjUyMzAiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzMCJ9LHsib2JqZWN0X2lkIjoiNTIyNiIsInNlcXVlbmNlX29yZGVyIjozLCJjYW5kaWRhdGVfaWQiOiI1MjI2In0seyJvYmplY3RfaWQiOiI1MjI5Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMjkifSx7Im9iamVjdF9pZCI6IjUyMjgiLCJzZXF1ZW5jZV9vcmRlciI6NiwiY2FuZGlkYXRlX2lkIjoiNTIyOCJ9LHsib2JqZWN0X2lkIjoiNTIyNSIsInNlcXVlbmNlX29yZGVyIjo3LCJjYW5kaWRhdGVfaWQiOiI1MjI1In0seyJvYmplY3RfaWQiOiI1MjI3Iiwic2VxdWVuY2Vfb3JkZXIiOjcsImNhbmRpZGF0ZV9pZCI6IjUyMjcifV19LHsiQHR5cGUiOiJDYW5kaWRhdGVDb250ZXN0Iiwib2JqZWN0X2lkIjoiNzc0Iiwic2VxdWVuY2Vfb3JkZXIiOjMsInZvdGVfdmFyaWF0aW9uIjoibl9vZl9tIiwibmFtZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJudW1iZXJfZWxlY3RlZCI6MTAsInZvdGVzX2FsbG93ZWQiOjEsImJhbGxvdF90aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IlZlcml0YXRpcyBuYW0gbWF4aW1lLiAxMzAiLCJsYW5ndWFnZSI6ImVuIn1dfSwiYmFsbG90X3N1YnRpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiPHA-RXQgdXQgdGVuZXR1ci4gMTMzPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIxOCIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjE4In0seyJvYmplY3RfaWQiOiI1MjI0Iiwic2VxdWVuY2Vfb3JkZXIiOjAsImNhbmRpZGF0ZV9pZCI6IjUyMjQifSx7Im9iamVjdF9pZCI6IjUyMjEiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIyMSJ9LHsib2JqZWN0X2lkIjoiNTIxNiIsInNlcXVlbmNlX29yZGVyIjo0LCJjYW5kaWRhdGVfaWQiOiI1MjE2In0seyJvYmplY3RfaWQiOiI1MjE5Iiwic2VxdWVuY2Vfb3JkZXIiOjQsImNhbmRpZGF0ZV9pZCI6IjUyMTkifSx7Im9iamVjdF9pZCI6IjUyMTUiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIxNSJ9LHsib2JqZWN0X2lkIjoiNTIyMyIsInNlcXVlbmNlX29yZGVyIjo1LCJjYW5kaWRhdGVfaWQiOiI1MjIzIn0seyJvYmplY3RfaWQiOiI1MjIyIiwic2VxdWVuY2Vfb3JkZXIiOjYsImNhbmRpZGF0ZV9pZCI6IjUyMjIifSx7Im9iamVjdF9pZCI6IjUyMTciLCJzZXF1ZW5jZV9vcmRlciI6OCwiY2FuZGlkYXRlX2lkIjoiNTIxNyJ9LHsib2JqZWN0X2lkIjoiNTIyMCIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjIwIn1dfSx7IkB0eXBlIjoiQ2FuZGlkYXRlQ29udGVzdCIsIm9iamVjdF9pZCI6Ijc3NiIsInNlcXVlbmNlX29yZGVyIjo0LCJ2b3RlX3ZhcmlhdGlvbiI6Im5fb2ZfbSIsIm5hbWUiOiJWb2x1cHRhdGVzIGNvcnJ1cHRpIG5vYmlzLiAyMDgiLCJudW1iZXJfZWxlY3RlZCI6OCwidm90ZXNfYWxsb3dlZCI6MSwiYmFsbG90X3RpdGxlIjp7InRleHQiOlt7InZhbHVlIjoiVm9sdXB0YXRlcyBjb3JydXB0aSBub2Jpcy4gMjA4IiwibGFuZ3VhZ2UiOiJlbiJ9XX0sImJhbGxvdF9zdWJ0aXRsZSI6eyJ0ZXh0IjpbeyJ2YWx1ZSI6IjxwPkFzcGVybmF0dXIgaXBzYW0gcXVpcy4gMjExPC9wPiIsImxhbmd1YWdlIjoiZW4ifV19LCJiYWxsb3Rfc2VsZWN0aW9ucyI6W3sib2JqZWN0X2lkIjoiNTIzMiIsInNlcXVlbmNlX29yZGVyIjowLCJjYW5kaWRhdGVfaWQiOiI1MjMyIn0seyJvYmplY3RfaWQiOiI1MjMxIiwic2VxdWVuY2Vfb3JkZXIiOjEsImNhbmRpZGF0ZV9pZCI6IjUyMzEifSx7Im9iamVjdF9pZCI6IjUyMzUiLCJzZXF1ZW5jZV9vcmRlciI6MiwiY2FuZGlkYXRlX2lkIjoiNTIzNSJ9LHsib2JqZWN0X2lkIjoiNTIzNiIsInNlcXVlbmNlX29yZGVyIjoyLCJjYW5kaWRhdGVfaWQiOiI1MjM2In0seyJvYmplY3RfaWQiOiI1MjM0Iiwic2VxdWVuY2Vfb3JkZXIiOjUsImNhbmRpZGF0ZV9pZCI6IjUyMzQifSx7Im9iamVjdF9pZCI6IjUyMzgiLCJzZXF1ZW5jZV9vcmRlciI6NSwiY2FuZGlkYXRlX2lkIjoiNTIzOCJ9LHsib2JqZWN0X2lkIjoiNTIzMyIsInNlcXVlbmNlX29yZGVyIjo4LCJjYW5kaWRhdGVfaWQiOiI1MjMzIn0seyJvYmplY3RfaWQiOiI1MjM3Iiwic2VxdWVuY2Vfb3JkZXIiOjgsImNhbmRpZGF0ZV9pZCI6IjUyMzcifV19XX0sIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjQyLmNyZWF0ZV9lbGVjdGlvbithLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.M7os6kWU5Ijp5zBWRE94BvwQ2_iS_fSq_nSKXQxA5G-GGt4X8Qwd2JjFcOhJ_jnIeIJE4-IGBEE3BSNeCXQ73VY2WokPKrMrhBiawepuDoNfjHmj_X5r48aGt6vy1A-u5-bJh0wQQvG6mSF9Xx2uHW8lY4aSgAEEIw9FCTLaqK4k38U0491A8f3u8Es4k4agY7ViBMdFCHWtsBt4bkAiUJXYe8OWucBXFSQA1C4JwsixAUPJ7LVpuAiYshIF-UPBrM-pe56bfslFTRIDY4pGRKQpJimZQfkP2kOoCRw6x4rgS8MXQ1L70pMqRq-pW39EHpcDN-nfgxNJwHxIk4GXUAeDTQT5cG5SjSVXUqiN5dhPWJZOofXQ4FyF1QEgGaBq_J3YJmT6jvNaj_8oUBEnJ8CHa3Yd963l2U7PdDdG6mRuhKRCWI3TbKfc3WoSmHosSTSD5ldexeH4DGs_-N-lO8UPQC5LFcbI0gxUnRa0oiWqkNIdqZx2pFwAsMtI5-afl7EsbnjFQlj6nruBKYcVGhraph5f4fAznHeJgutju7iUXPI2gmyemNcneXP8daNPxn6O0XEe52CDlTZl8V_rzmaizQ3BJNO3DsspRvS55lSsH_ce_3ElGMiFmf5cgt00RmUiTXjLN59gvIsZgQhfD7YxWXUdhkO1stSxCXuuT60\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_214060\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"254cb984da3479e99fd26dc4dc425048\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["2267fbb1-1837-45ee-97d6-d511e7e71456"],"X-Runtime":["0.934773"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"createElection\":{\"election\":{\"status\":\"key_ceremony\"},\"error\":null}}}"}},"recorded_at":"Wed, 13 Jan 2021 18:00:26 GMT"},{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"query GraphQL__Client__OperationDefinition_241580 {\\n election(uniqueId: \\\"decidim-test-authority.42\\\") {\\n status\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_241580\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"5774e54d8ac30ead0aa14c4bd33653b9\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["3bf5ec60-6be5-45c1-99b4-93df5257c11b"],"X-Runtime":["0.015839"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"election\":{\"status\":\"ready\"}}}"}},"recorded_at":"Wed, 13 Jan 2021 18:00:43 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/Vote_in_an_election/behaves_like_allows_to_vote/behaves_like_uses_the_voting_booth/uses_the_voting_booth.json b/decidim-elections/spec/cassettes/Vote_in_an_election/behaves_like_allows_to_vote/behaves_like_uses_the_voting_booth/uses_the_voting_booth.json new file mode 100644 index 0000000000000..c87852a54649d --- /dev/null +++ b/decidim-elections/spec/cassettes/Vote_in_an_election/behaves_like_allows_to_vote/behaves_like_uses_the_voting_booth/uses_the_voting_booth.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_164680 {\\n vote(messageId: \\\"decidim-test-authority.10002.vote.cast+v.2b4ed424cb13184168b02ff54e0fc1d00ca666548007e5309c95faa92fe4e0f4\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJjb250ZW50Ijoie1wiYmFsbG90X3N0eWxlXCI6XCJ1bmlxdWVcIixcInF1ZXN0aW9uXzg2MVwiOltcIjU4MDhcIl0sXCJxdWVzdGlvbl84NjNcIjpbXCI1ODI0XCIsXCI1ODIxXCIsXCI1ODE5XCJdLFwicXVlc3Rpb25fODYyXCI6W1wiNTgxOFwiLFwiNTgxNVwiLFwiNTgxN1wiLFwiNTgxNFwiLFwiNTgxMlwiXX0iLCJpYXQiOjE2MTA1Njc4NDQsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAyLnZvdGUuY2FzdCt2LjJiNGVkNDI0Y2IxMzE4NDE2OGIwMmZmNTRlMGZjMWQwMGNhNjY2NTQ4MDA3ZTUzMDljOTVmYWE5MmZlNGUwZjQifQ.eSnPvE3_Ty6IkTX-DFRGkH1tfBzpuFlqdgPo2EVz8laCznNN_xb_EwbMPHAtH0hrB0vT5slrPaM1HczUxylbrOIOwvziisccGe4Ey-mOaM56w8SicIJJjmx6E6f1LeRukoTkzvO2Qmn2U13Majzx0LPkkVTwIJw9rkgvIDfVre0l0qUn_yRlV2opJslmLPpjtBTZ-xvi35qu_J5C2GJzVAvb31CMgZUAKMLx75wyfqy2O0Els6C-jItMCR0xStfkNnI5QS5IkgA4L-rNLkOBEEuq2R4UJ4qiPHr2ZiFIUxP_g-Xd7fwRxQfzgt0dgxEVZYPE2DvtsAmaSEOT0VMymkjjsZzp7CtQf5dYEdXrpN4BRAf64gQ13MPh6NOnrLWlYNqlR7KhmxK4lr0LgosFdbD6xhXN8n37lNHz-u2CWs2XJgIDpEe9TOSlCj0GPpjV4jwRCrPUOtfx8_fUaxs2NhPrMTZGgshA7v_W4SGFZWb3ughPXV8XF5tauDnLTB2Ln-GKO9A4mQ-Wh1OXfUsdv_wLidtIOotzf62oujAHCJQHtlhMNsU-dLDpLlyxeHoaBY16j2pM46ufle7nCOMxg3TSZtfAuGoIjiAf9lRXyHRf5MLFPLVitO2-zV3xS4xPRL7WC7YvvpAGHK5Opq4xG_cnXht38VJrBIAuIbWL1hU\\\") {\\n pendingMessage {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_164680\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"2c609107c088369467170808644ee7a0\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["f9f150f0-edc5-4962-bf5b-24b8f5ebda87"],"X-Runtime":["0.147028"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"vote\":{\"pendingMessage\":{\"status\":\"enqueued\"},\"error\":null}}}"}},"recorded_at":"Wed, 13 Jan 2021 19:57:25 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_close/closes_the_Ballot_Box.json b/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_close/closes_the_Ballot_Box.json new file mode 100644 index 0000000000000..9fd82b2d8a1a6 --- /dev/null +++ b/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_close/closes_the_Ballot_Box.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_126280 {\\n closeBallotBox(messageId: \\\"decidim-test-authority.10002.close_ballot_box+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2Mzc2MDksIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAyLmNsb3NlX2JhbGxvdF9ib3grYS5kZWNpZGltLXRlc3QtYXV0aG9yaXR5In0.CP1xw2L7nMrH-TNxBpp3wzUbKflUF1PMc6cUdjdKX2yDb8ruQeL2UwWqmP7a9_bR4eiXxdWROX2BczENfJ18z7sIm2BSGQk4tOTdOMq2ymgUgRHd3lHdEfJeohP8XlY_grcIAbOctq3Ury0aUlHs-FulN260QRD0uiU3-sGizqPUbuLNdLRUeWn7Gi2rICYjaJnEcap-aHqNOEQp1vhOsPJh66oZSvaRxkcs2jmTdjzVji7-Jr_BMXbQz2uBKKwaPxD8qXvA0PVN2l1TxFcDrkpws-J6gqTZ4fEHW5_psEZh0XNkHlK_brKEpTlu_qUsYB-iPTyoNj9lD0Vsz1u1mRC3rPI24APNdZIzQ7Gu3MM5s_-wUopQh_63XXhPKjotAKrEYdYD1NYEfRKRub5g1YteNBFuiHChblc8h8--5fjCWyVMqeXYO2gP-FqpEOoRw27CbspCnm7B0VNnAgcZH5XKmBmC4kflxD6lGRuzeC6dAS1qYnq32IG2KrPlfFZmO8P_fEUlANHj16OvRCt0C-qsvdlupRkFJoEZ_ElHKM3yybk8HVP_ZbV2bKfd9rAh6xqs1JvsVxra6l03AE-ll7GVJG4Gxft3PWbAiR8yPnhjCko2vbyYs7MAX3klVDk5T2yoV9sb7_cYDB8hJc9saomIghdSXkBigHHkp9Sc7l0\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_126280\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"d88c83daf267765e702b4d923de5f6a0\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["d79e20ca-57f4-4c5f-aa67-c52a9731be2a"],"X-Runtime":["0.737252"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"closeBallotBox\":{\"election\":{\"status\":\"tally\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 11:46:51 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_open/opens_the_Ballot_Box.json b/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_open/opens_the_Ballot_Box.json new file mode 100644 index 0000000000000..c46428709335d --- /dev/null +++ b/decidim-elections/spec/cassettes/decidim_elections_scheduled_tasks/with_elections_to_open/opens_the_Ballot_Box.json @@ -0,0 +1 @@ +{"http_interactions":[{"request":{"method":"post","uri":"http://bulletin-board.lvh.me:8000/api","body":{"encoding":"UTF-8","string":"{\"query\":\"mutation GraphQL__Client__OperationDefinition_127780 {\\n openBallotBox(messageId: \\\"decidim-test-authority.10001.open_ballot_box+a.decidim-test-authority\\\", signedData: \\\"eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MDg2Mzc2MTIsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAxLm9wZW5fYmFsbG90X2JveCthLmRlY2lkaW0tdGVzdC1hdXRob3JpdHkifQ.bfbYdacCQF3_1-8HZcVjAfeCqLnH_f2yuLEoWIjF7w11_Noc-V4ut4XQr1_5paQ7VIKbUSy3di6LK4KO9gybyn_gaNZD0DpoJENND8dqWk2BZJKb4mWAN0-jxih39i0Rc4kDYThUUOs68WuLNGewHiesh9iXOeZ84KKSlhq6i5ZLIvDf59iZhJoKv-xvRMtaSUwDnko_8JuuQ9bTQbnu2e3pBjZqs6qxsM6_VwPibYeAECud7J1gK1XN7HZllOMtwF-p_3VzOoGFfOR7i7t9w26KtJH6oP8blLHNt6Vuu3BGnKVmrPdCuyulZlwjLniFxqi2Ta3uJLYGVeLVca3IbpU2tjAEPkn6bQdVXvCvxNuKZ5ulf2xxpCzNv6Bt0EVLTanOaHkjqSAs0L7OC6Pj_DbzDR9pWInzo6Bvg7Hbewoaxe_GiiXzC-zlh0o_b2Ra58DxKlYebkUcr4X764W3IF_QSYLBtMmxHAGjFQxfqt8T-vF4jtgSH1u2M6hfpmBzURrIplDrcXx-5053o3tsmSj3HQR7HCM1qPL4jNUMKKrBCSnM0HYRU66DlmIOOc7uZWYnvwwzSJWBMvJOU5OJJP93Ce-4Z735iKtwbmnI5pADDPTncaB-da9N0Wldui_YORB8azVaWJnPEYPLrBCHdhEs_7rncbcXz97sLWqplSs\\\") {\\n election {\\n status\\n }\\n error\\n }\\n}\",\"operationName\":\"GraphQL__Client__OperationDefinition_127780\",\"variables\":{}}"},"headers":{"Authorization":["89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G"],"User-Agent":["Faraday v1.0.1"],"Content-Type":["application/json"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],"Accept":["*/*"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"X-Frame-Options":["SAMEORIGIN"],"X-Xss-Protection":["1; mode=block"],"X-Content-Type-Options":["nosniff"],"X-Download-Options":["noopen"],"X-Permitted-Cross-Domain-Policies":["none"],"Referrer-Policy":["strict-origin-when-cross-origin"],"Content-Type":["application/json; charset=utf-8"],"Etag":["W/\"848ecc80a77c81d45e9e1704860ef0f5\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Request-Id":["bac7dff9-faec-4eb1-9997-97605aeef212"],"X-Runtime":["0.053974"],"Vary":["Origin"],"Transfer-Encoding":["chunked"]},"body":{"encoding":"UTF-8","string":"{\"data\":{\"openBallotBox\":{\"election\":{\"status\":\"vote\"},\"error\":null}}}"}},"recorded_at":"Tue, 22 Dec 2020 11:46:53 GMT"}],"recorded_with":"VCR 6.0.0"} \ No newline at end of file diff --git a/decidim-elections/spec/cells/decidim/votings/voting_cell_spec.rb b/decidim-elections/spec/cells/decidim/votings/voting_cell_spec.rb new file mode 100644 index 0000000000000..cebce464c84d7 --- /dev/null +++ b/decidim-elections/spec/cells/decidim/votings/voting_cell_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Votings::VotingCell, type: :cell do + controller Decidim::Votings::VotingsController + + subject { my_cell.call } + + let(:my_cell) { cell("decidim/votings/voting", model) } + let!(:voting) { create(:voting, :published, :ongoing) } + let!(:current_user) { create(:user, :confirmed, organization: model.organization) } + + before do + allow(controller).to receive(:current_user).and_return(current_user) + end + + context "when rendering a voting" do + let(:model) { voting } + + it "renders the card" do + expect(subject).to have_css(".card--voting") + end + + it "renders the title and text" do + expect(subject).to have_css(".card__title") + expect(subject).to have_css(".card__text") + end + end +end diff --git a/decidim-elections/spec/cells/decidim/votings/voting_m_cell_spec.rb b/decidim-elections/spec/cells/decidim/votings/voting_m_cell_spec.rb new file mode 100644 index 0000000000000..c8dff65c8229b --- /dev/null +++ b/decidim-elections/spec/cells/decidim/votings/voting_m_cell_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/core/test/shared_examples/space_cell_changes_button_text_cta" + +module Decidim::Votings + describe VotingMCell, type: :cell do + controller Decidim::Votings::VotingsController + + subject { cell_html } + + let(:my_cell) { cell("decidim/votings/voting_m", voting, context: { show_space: show_space }) } + let(:cell_html) { my_cell.call } + let(:start_time) { 2.days.ago } + let(:end_time) { 1.day.from_now } + let!(:voting) { create(:voting, :published, start_time: start_time, end_time: end_time) } + let(:model) { voting } + let(:user) { create :user, organization: voting.organization } + + before do + allow(controller).to receive(:current_user).and_return(user) + end + + context "when rendering" do + let(:show_space) { false } + + it "renders the card" do + expect(subject).to have_css(".card--voting") + end + + it "renders the start and end time " do + voting_start = I18n.l(start_time.to_date, format: :decidim_short) + voting_end = I18n.l(end_time.to_date, format: :decidim_short) + + expect(subject).to have_css(".card-data__item--centerblock", text: voting_start) + expect(subject).to have_css(".card-data__item--centerblock", text: voting_end) + end + + it "renders the title and description" do + description = strip_tags(translated(voting.description, locale: :en)) + expect(subject).to have_css(".card__title", text: translated(voting.title)) + expect(subject).to have_css(".card__text", text: description) + end + + it "renders the badge name" do + expect(subject).to have_css(".card__text--status", text: "Ongoing") + end + + it "renders the banner image" do + expect(subject).to have_css(".card__image") + end + end + end +end diff --git a/decidim-elections/spec/commands/decidim/elections/admin/close_ballot_box_spec.rb b/decidim-elections/spec/commands/decidim/elections/admin/close_ballot_box_spec.rb new file mode 100644 index 0000000000000..53d48325f4ce1 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/elections/admin/close_ballot_box_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Admin::CloseBallotBox do + subject { described_class.new(form) } + + let(:organization) { create :organization, available_locales: [:en, :ca, :es], default_locale: :en } + let(:invalid) { false } + let(:participatory_process) { create :participatory_process, organization: organization } + let(:current_component) { create :component, participatory_space: participatory_process, manifest_name: "elections" } + let(:user) { create :user, :admin, :confirmed, organization: organization } + let(:election) { create :election, :vote } + let(:form) do + double( + invalid?: invalid, + election: election, + current_user: user, + current_component: current_component, + current_organization: organization, + bulletin_board: bulletin_board + ) + end + + let(:method_name) { :close_ballot_box } + let(:response) { OpenStruct.new(status: "tally") } + + let(:bulletin_board) do + double(Decidim::Elections.bulletin_board) + end + + before do + allow(bulletin_board).to receive(:public_key).and_return({ + "kty": "RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e": "AQAB", + "alg": "RS256", + "kid": "2011-04-29" + }) + allow(bulletin_board).to receive(:authority_name).and_return("Decidim Test Authority") + allow(bulletin_board).to receive(:authority_slug).and_return("decidim-test-authority") + allow(bulletin_board).to receive(method_name).and_return(response) + end + + context "when valid form" do + it "updates the election status" do + expect { subject.call }.to change { election.reload.bb_status }.from("vote").to("tally") + end + + it "logs the performed action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with(:close_ballot_box, election, user, visibility: "all") + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + end + + it "calls the bulletin board create_election method with the correct params" do + subject.call + expect(bulletin_board).to have_received(method_name).with(election.id) + end + end + + context "when the form is not valid" do + let(:invalid) { true } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the bulletin board returns an error message" do + before do + allow(bulletin_board).to receive(method_name).and_raise(StandardError.new("An error!")) + end + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid, "An error!") + end + end +end diff --git a/decidim-elections/spec/commands/decidim/elections/admin/open_ballot_box_spec.rb b/decidim-elections/spec/commands/decidim/elections/admin/open_ballot_box_spec.rb new file mode 100644 index 0000000000000..2619a721e46b9 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/elections/admin/open_ballot_box_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Admin::OpenBallotBox do + subject { described_class.new(form) } + + let(:organization) { create :organization, available_locales: [:en, :ca, :es], default_locale: :en } + let(:invalid) { false } + let(:participatory_process) { create :participatory_process, organization: organization } + let(:current_component) { create :component, participatory_space: participatory_process, manifest_name: "elections" } + let(:user) { create :user, :admin, :confirmed, organization: organization } + let(:election) { create :election, :ready } + let(:form) do + double( + invalid?: invalid, + election: election, + current_user: user, + current_component: current_component, + current_organization: organization, + bulletin_board: bulletin_board + ) + end + + let(:method_name) { :open_ballot_box } + let(:response) { OpenStruct.new(status: "vote") } + + let(:bulletin_board) do + double(Decidim::Elections.bulletin_board) + end + + before do + allow(bulletin_board).to receive(:public_key).and_return({ + "kty": "RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e": "AQAB", + "alg": "RS256", + "kid": "2011-04-29" + }) + allow(bulletin_board).to receive(:authority_name).and_return("Decidim Test Authority") + allow(bulletin_board).to receive(:authority_slug).and_return("decidim-test-authority") + allow(bulletin_board).to receive(method_name).and_return(response) + end + + context "when valid form" do + it "updates the election status" do + expect { subject.call }.to change(election, :bb_status).from("ready").to("vote") + end + + it "logs the performed action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with(:open_ballot_box, election, user, visibility: "all") + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + end + + it "calls the bulletin board create_election method with the correct params" do + subject.call + expect(bulletin_board).to have_received(method_name).with(election.id) + end + end + + context "when the form is not valid" do + let(:invalid) { true } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the bulletin board returns an error message" do + before do + allow(bulletin_board).to receive(method_name).and_raise(StandardError.new("An error!")) + end + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid, "An error!") + end + end +end diff --git a/decidim-elections/spec/commands/decidim/elections/admin/setup_election_spec.rb b/decidim-elections/spec/commands/decidim/elections/admin/setup_election_spec.rb index 7d515c7bb68bf..74404a42e8255 100644 --- a/decidim-elections/spec/commands/decidim/elections/admin/setup_election_spec.rb +++ b/decidim-elections/spec/commands/decidim/elections/admin/setup_election_spec.rb @@ -3,17 +3,16 @@ require "spec_helper" describe Decidim::Elections::Admin::SetupElection do - subject { described_class.new(form, bulletin_board: bulletin_board) } + subject { described_class.new(form) } let(:organization) { create :organization, available_locales: [:en, :ca, :es], default_locale: :en } let(:invalid) { false } let(:participatory_process) { create :participatory_process, organization: organization } let(:current_component) { create :component, participatory_space: participatory_process, manifest_name: "elections" } let(:user) { create :user, :admin, :confirmed, organization: organization } - let(:election) { create :election, :complete } - let(:trustees) { create_list :trustee, 5, :considered, :with_public_key } + let!(:election) { create :election, :complete } + let(:trustees) { create_list :trustee, 5, :with_public_key } let(:trustee_ids) { trustees.pluck(:id) } - let(:errors) { double.as_null_object } let(:form) do double( invalid?: invalid, @@ -22,7 +21,7 @@ current_component: current_component, current_organization: organization, trustee_ids: trustee_ids, - errors: errors + bulletin_board: bulletin_board ) end let(:scheme) do @@ -33,37 +32,62 @@ } } end - let(:status) { OpenStruct.new(status: "key_ceremony") } - let(:response) do - OpenStruct.new(election: status, error: nil) - end + let(:method_name) { :create_election } + let(:response) { OpenStruct.new(status: "key_ceremony") } let(:bulletin_board) do double(Decidim::Elections.bulletin_board) end before do + allow(bulletin_board).to receive(:public_key).and_return({ + "kty": "RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e": "AQAB", + "alg": "RS256", + "kid": "2011-04-29" + }) + allow(bulletin_board).to receive(:authority_name).and_return("Decidim Test Authority") allow(bulletin_board).to receive(:authority_slug).and_return("decidim-test-authority") allow(bulletin_board).to receive(:scheme).and_return(scheme) - allow(bulletin_board).to receive(:setup_election).and_return(response) + allow(bulletin_board).to receive(method_name).and_return(response) end context "when valid form" do - let(:trustee_users) { trustees.collect(&:user) } + it "updates the election status" do + expect { subject.call }.to change { Decidim::Elections::Election.last.bb_status }.from(nil).to("key_ceremony") + end + + it "logs the performed action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with(:setup, election, user, visibility: "all") + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + end + + it "adds the trustees to the election" do + expect { subject.call }.to change { election.trustees.count }.by(5) + end - it "setup the election" do + it "notifies the trustees" do expect(Decidim::EventsManager) .to receive(:publish) .with( event: "decidim.events.elections.trustees.new_election", event_class: Decidim::Elections::Trustees::NotifyTrusteeNewElectionEvent, resource: election, - affected_users: trustee_users + affected_users: trustees.collect(&:user) ) + subject.call + end - expect { subject.call }.to change { election.trustees.count }.by(5) + it "blocks the election for modifications" do + expect { subject.call }.to change(election, :blocked?).from(false).to(true) expect(election.blocked_at).to be_within(1.second).of election.updated_at - expect(election.blocked?).to be true end end @@ -76,17 +100,12 @@ end context "when the bulletin board returns an error message" do - let(:response) do - OpenStruct.new(election: nil, error: "An error!") + before do + allow(bulletin_board).to receive(method_name).and_raise(StandardError.new("An error!")) end it "is not valid" do - expect { subject.call }.to broadcast(:invalid) - end - - it "returns the error message" do - expect(form.errors).to receive(:add).with(:base, "An error!") - subject.call + expect { subject.call }.to broadcast(:invalid, "An error!") end end end diff --git a/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status_spec.rb b/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status_spec.rb new file mode 100644 index 0000000000000..a4ec1464c9c23 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_election_bulletin_board_status_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::TrusteeZone::UpdateElectionBulletinBoardStatus do + subject { described_class.new(election, required_status) } + + let(:election) { create :election, :created } + let(:required_status) { :key_ceremony } + let(:new_status) { :ready } + let(:response) { new_status } + + before do + allow(Decidim::Elections.bulletin_board).to receive(:get_status).and_return(response) + end + + it "broadcasts ok" do + expect { subject.call }.to broadcast(:ok) + end + + it "updates the election status" do + subject.call + expect(election).to be_bb_ready + end + + context "when the election status doesn't match the required status" do + let(:election) { create :election, :results } + + it "broadcasts ok" do + expect { subject.call }.to broadcast(:ok) + end + + it "doesn't update the election status" do + subject.call + expect(election).to be_bb_results + end + end +end diff --git a/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_trustee_spec.rb b/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_trustee_spec.rb index d2c60dc2a498e..820dfaec6980a 100644 --- a/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_trustee_spec.rb +++ b/decidim-elections/spec/commands/decidim/elections/trustee_zone/update_trustee_spec.rb @@ -10,10 +10,12 @@ double( invalid?: invalid, public_key: public_key, - trustee: trustee + trustee: trustee, + name: trustee_name ) end let(:public_key) { "asadasfdafadssda" } + let(:trustee_name) { "Sheldon" } let(:invalid) { false } it "updates the trustee" do diff --git a/decidim-elections/spec/commands/decidim/elections/voter/cast_vote_spec.rb b/decidim-elections/spec/commands/decidim/elections/voter/cast_vote_spec.rb new file mode 100644 index 0000000000000..e0c32dbba0315 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/elections/voter/cast_vote_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Voter::CastVote do + subject { described_class.new(form) } + + let(:form) do + double( + invalid?: invalid, + encrypted_vote: encrypted_vote, + encrypted_vote_hash: encrypted_vote_hash, + election: election, + election_id: election_id, + election_unique_id: election_unique_id, + voter_id: voter_id, + bulletin_board: bulletin_board + ) + end + let(:invalid) { false } + let(:encrypted_vote) { { question_1: "aNsWeR 1" }.to_json } + let(:encrypted_vote_hash) { "1234" } + let(:election) { create(:election) } + let(:election_id) { election.id } + let(:election_unique_id) { "decidim-test-authority.#{election.id}" } + let(:voter_id) { "voter.1" } + + let(:method_name) { :cast_vote } + let(:response) { OpenStruct.new(id: 1, status: "enqueued") } + let(:bulletin_board) do + double(Decidim::Elections.bulletin_board) + end + + before do + allow(bulletin_board).to receive(method_name).and_return(response) + end + + it "broadcasts ok" do + expect { subject.call }.to broadcast(:ok) + end + + it "stores the vote" do + expect { subject.call }.to change(Decidim::Elections::Vote, :count).by(1) + + last_vote = Decidim::Elections::Vote.last + expect(last_vote.election).to eq(election) + expect(last_vote.voter_id).to eq("voter.1") + expect(last_vote.encrypted_vote_hash).to eq("1234") + expect(last_vote.status).to eq(Decidim::Elections::Vote::PENDING_STATUS) + end + + it "calls the bulletin board cast_vote method with the correct params" do + subject.call + expect(bulletin_board).to have_received(method_name).with(election_id, voter_id, encrypted_vote) + end + + context "when the form is not valid" do + let(:invalid) { true } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the bulletin board returns an error message" do + before do + allow(bulletin_board).to receive(method_name).and_raise(StandardError.new("An error!")) + end + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid, "An error!") + end + end +end diff --git a/decidim-elections/spec/commands/decidim/votings/admin/create_voting_spec.rb b/decidim-elections/spec/commands/decidim/votings/admin/create_voting_spec.rb index 40e28d93c2591..463274be92d56 100644 --- a/decidim-elections/spec/commands/decidim/votings/admin/create_voting_spec.rb +++ b/decidim-elections/spec/commands/decidim/votings/admin/create_voting_spec.rb @@ -18,7 +18,9 @@ slug: slug, scope: scope, current_user: user, - current_organization: organization + current_organization: organization, + banner_image: nil, + introductory_image: nil ) end diff --git a/decidim-elections/spec/commands/decidim/votings/admin/publish_voting_spec.rb b/decidim-elections/spec/commands/decidim/votings/admin/publish_voting_spec.rb new file mode 100644 index 0000000000000..b84ae497c8d72 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/votings/admin/publish_voting_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Votings + module Admin + describe PublishVoting do + subject { described_class.new(voting, current_user) } + + let(:voting) { create :voting, :unpublished } + let(:current_user) { create :user, :admin, organization: voting.organization } + + context "when the voting is nil" do + let(:voting) { nil } + let(:current_user) { nil } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the voting is published" do + let(:voting) { create :voting, :published } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the voting is not published" do + it "is valid" do + expect { subject.call }.to broadcast(:ok) + end + + it "publishes it" do + subject.call + voting.reload + expect(voting).to be_published + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with( + :publish, + voting, + current_user, + visibility: "all" + ) + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + expect(action_log.version.event).to eq "update" + end + end + end + end + end +end diff --git a/decidim-elections/spec/commands/decidim/votings/admin/unpublish_voting_spec.rb b/decidim-elections/spec/commands/decidim/votings/admin/unpublish_voting_spec.rb new file mode 100644 index 0000000000000..c346f90af3723 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/votings/admin/unpublish_voting_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Votings + module Admin + describe UnpublishVoting do + subject { described_class.new(voting, current_user) } + + let(:voting) { create :voting, :published } + let(:current_user) { create :user, :admin, organization: voting.organization } + + context "when the voting is nil" do + let(:voting) { nil } + let(:current_user) { nil } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the voting is not published" do + let(:voting) { create :voting, :unpublished } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when the voting is published" do + it "is valid" do + expect { subject.call }.to broadcast(:ok) + end + + it "unpublishes it" do + subject.call + voting.reload + expect(voting).not_to be_published + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with( + :unpublish, + voting, + current_user, + visibility: "all" + ) + .and_call_original + + expect { subject.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.version).to be_present + expect(action_log.version.event).to eq "update" + end + end + end + end + end +end diff --git a/decidim-elections/spec/commands/decidim/votings/admin/update_voting_spec.rb b/decidim-elections/spec/commands/decidim/votings/admin/update_voting_spec.rb new file mode 100644 index 0000000000000..57ffb36ad85e3 --- /dev/null +++ b/decidim-elections/spec/commands/decidim/votings/admin/update_voting_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Votings + module Admin + describe UpdateVoting do + let(:voting) { create :voting } + let(:params) do + { + voting: { + id: voting.id, + title_en: "Foo title", + title_ca: "Foo title", + title_es: "Foo title", + description_en: voting.description["en"], + description_ca: voting.description["ca"], + description_es: voting.description["es"], + slug: voting.slug, + banner_image: voting.banner_image, + decidim_scope_id: voting.scope.id, + start_time: voting.start_time, + end_time: voting.end_time, + introductory_image: voting.introductory_image + } + } + end + let(:context) do + { + current_organization: voting.organization, + voting_id: voting.id + } + end + let(:form) { VotingForm.from_params(params).with_context(context) } + let(:command) { described_class.new(voting, form) } + + describe "when the form is not valid" do + before do + expect(form).to receive(:invalid?).and_return(true) + end + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + + it "doesn't update the voting" do + command.call + voting.reload + + expect(voting.title["en"]).not_to eq("Foo title") + end + end + + describe "when the voting is not valid" do + before do + expect(form).to receive(:invalid?).and_return(false) + expect(voting).to receive(:valid?).at_least(:once).and_return(false) + voting.errors.add(:banner_image, "Image too big") + end + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + + it "adds errors to the form" do + command.call + + expect(form.errors[:banner_image]).not_to be_empty + end + end + + describe "when the form is valid" do + it "broadcasts ok" do + expect { command.call }.to broadcast(:ok) + end + + it "updates the voting" do + expect { command.call }.to broadcast(:ok) + voting.reload + + expect(voting.title["en"]).to eq("Foo title") + end + + context "when banner image is not updated" do + it "does not replace the banner image" do + expect(voting).not_to receive(:banner_image=) + + command.call + voting.reload + + expect(voting.banner_image).to be_present + end + end + + context "when introductory image is not updated" do + it "does not replace the introductory image" do + expect(voting).not_to receive(:introductory_image=) + + command.call + voting.reload + + expect(voting.introductory_image).to be_present + end + end + end + end + end + end +end diff --git a/decidim-elections/spec/forms/decidim/elections/admin/ballot_box_form_spec.rb b/decidim-elections/spec/forms/decidim/elections/admin/ballot_box_form_spec.rb new file mode 100644 index 0000000000000..9efc137c487c8 --- /dev/null +++ b/decidim-elections/spec/forms/decidim/elections/admin/ballot_box_form_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Admin::BallotBoxForm do + subject { described_class.from_params(attributes).with_context(context) } + + let(:context) do + { + current_organization: component.organization, + current_component: component, + election: election, + current_step: current_step + } + end + let(:component) { election.component } + let(:current_step) { election.bb_status } + let(:attributes) { {} } + + describe "for an election ready to start" do + let(:election) { create :election, :ready, start_time: start_time } + let(:start_time) { 1.hour.from_now } + let(:formatted_start_time) { I18n.l(start_time, format: :long) } + + it { is_expected.to be_valid } + + it "shows a message" do + expect(subject.messages).to eq({ + time_before: "The election will start soon. You can open the ballot box manually, or it will be opened automatically before the starting time, at #{formatted_start_time}." + }) + end + + context "when the election is not going to start soon" do + let(:start_time) { 10.days.from_now } + + it { is_expected.to be_invalid } + + it "shows an error message" do + subject.valid? + expect(subject.errors.messages).to eq({ + time_before: ["The election is ready to start. You have to wait until 6 hours before the starting time (#{formatted_start_time}) to open the ballot box."] + }) + end + end + end + + describe "for an election recently finished" do + let(:election) { create :election, :vote, end_time: end_time } + let(:end_time) { 1.minute.ago } + let(:formatted_end_time) { I18n.l(end_time, format: :long) } + + it { is_expected.to be_valid } + + it "shows a message" do + expect(subject.messages).to eq({ + time_after: "The election has ended. You can close the ballot box manually, or it will be closed automatically in a few minutes." + }) + end + + context "when the election didn't finish yet" do + let(:end_time) { 1.day.from_now } + + it { is_expected.to be_invalid } + + it "shows an error message" do + subject.valid? + expect(subject.errors.messages).to eq({ + time_after: ["The election is still ongoing. You have to wait until the ending time (#{formatted_end_time}) to close the ballot box."] + }) + end + end + end +end diff --git a/decidim-elections/spec/forms/decidim/elections/admin/setup_form_spec.rb b/decidim-elections/spec/forms/decidim/elections/admin/setup_form_spec.rb new file mode 100644 index 0000000000000..026d6d63d72ab --- /dev/null +++ b/decidim-elections/spec/forms/decidim/elections/admin/setup_form_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Admin::SetupForm do + subject(:form) { described_class.from_params(attributes).with_context(context) } + + let(:context) do + { + current_organization: component.organization, + current_component: component, + election: election, + current_step: "create_election" + } + end + let(:election) { create :election, :ready_for_setup, trustee_keys: [] } + let(:component) { election.component } + let(:attributes) do + { + setup: { + trustee_ids: trustee_ids + } + } + end + let!(:trustees) { create_list :trustee, 5, :with_public_key, election: election } + let(:trustee_ids) { trustees.pluck(:id) } + + it { is_expected.to be_valid } + + it "shows messages" do + expect(subject.messages).to eq({ + max_selections: "All the questions have a correct value for maximum of answers.", + minimum_answers: "Each question has at least 2 answers.", + minimum_questions: "The election has at least 1 question.", + published: "The election is published.", + time_before: "The setup is being done at least 3 hours before the election starts.", + trustees_number: "The participatory space has at least 2 trustees with public key." + }) + end + + context "when the election is not ready for the setup" do + let(:election) { create :election } + + it { is_expected.to be_invalid } + + it "shows errors" do + subject.valid? + expect(subject.errors.messages).to eq({ + minimum_questions: ["The election must have at least one question."], + published: ["The election is not published."] + }) + end + end + + context "when there are no trustees for the election" do + let(:trustees) { [] } + + it { is_expected.to be_invalid } + + it "shows errors" do + subject.valid? + expect(subject.errors.messages).to eq({ + trustees_number: ["The participatory space must have at least 2 trustees with public key."] + }) + end + end + + context "when the trustee_ids are not initialized" do + let(:attributes) { {} } + + it { is_expected.to be_valid } + + it "choose random trustees" do + expect(subject.trustees).to be_any + end + end + + describe ".participatory_space_trustees" do + subject { form.participatory_space_trustees.pluck(:id) } + + let!(:other_trustees) { create_list :trustee, 3, :with_public_key } + + it { is_expected.to match_array(trustees.pluck(:id)) } + end +end diff --git a/decidim-elections/spec/forms/decidim/elections/trustee_zone/trustee_form_spec.rb b/decidim-elections/spec/forms/decidim/elections/trustee_zone/trustee_form_spec.rb index cf44ed4424049..a7c0681b64db3 100644 --- a/decidim-elections/spec/forms/decidim/elections/trustee_zone/trustee_form_spec.rb +++ b/decidim-elections/spec/forms/decidim/elections/trustee_zone/trustee_form_spec.rb @@ -7,10 +7,12 @@ let(:trustee) { create(:trustee, public_key: public_key) } let(:public_key) { nil } + let(:trustee_name) { "Shelton Runolfsson Sr." } let(:new_public_key) { "1234567890abcde" } let(:attributes) do { - public_key: new_public_key + public_key: new_public_key, + name: trustee_name } end let(:context) do @@ -32,4 +34,10 @@ it { is_expected.not_to be_valid } end + + context "when the trustee already has a name" do + let(:trustee) { create(:trustee, public_key: public_key, name: "Sheldon") } + + it { is_expected.not_to be_valid } + end end diff --git a/decidim-elections/spec/forms/decidim/elections/voter/encrypted_vote_form_spec.rb b/decidim-elections/spec/forms/decidim/elections/voter/encrypted_vote_form_spec.rb new file mode 100644 index 0000000000000..939d23c6aa08f --- /dev/null +++ b/decidim-elections/spec/forms/decidim/elections/voter/encrypted_vote_form_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Voter::EncryptedVoteForm do + subject { described_class.from_params(params).with_context(context) } + + let(:params) do + { + encrypted_vote: "{ \"question_1\": \"aNsWeR 1\" }", + encrypted_vote_hash: "f149b928f7a00eae7e634fc5db0c3cc5531eefb81f49febce8da5bb4a153548b" + } + end + let(:context) do + { + current_user: user, + election: election + } + end + let(:user) { create(:user) } + let(:election) { create(:election) } + + context "when everything is fine" do + it { is_expected.to be_valid } + end + + context "when the encrypted vote is not present" do + let(:params) do + { + encrypted_vote_hash: "f149b928f7a00eae7e634fc5db0c3cc5531eefb81f49febce8da5bb4a153548b" + } + end + + it { is_expected.to be_invalid } + end + + context "when the encrypted vote hash is not present" do + let(:params) do + { + encrypted_vote: "{ \"question_1\": \"aNsWeR 1\" }" + } + end + + it { is_expected.to be_invalid } + end + + context "when the encrypted vote hash doesn't match" do + let(:params) do + { + encrypted_vote: "{ \"question_1\": \"aNsWeR 1\" }", + encrypted_vote_hash: "1234" + } + end + + it { is_expected.to be_invalid } + end + + context "when the current user is not present" do + let(:context) do + { + election: create(:election) + } + end + + it { is_expected.to be_invalid } + end + + context "when the election is not present" do + let(:context) do + { + current_user: create(:user) + } + end + + it { is_expected.to be_invalid } + end + + describe ".election_unique_id" do + it "returns the election unique id" do + expect(subject.election_unique_id).to eq("decidim-test-authority.#{election.id}") + end + end + + describe ".voter_id" do + it "returns the voter unique id" do + expect(subject.voter_id).to eq(Digest::SHA256.hexdigest([user.created_at, user.id, election.id, "decidim-test-authority"].join("."))) + end + end +end diff --git a/decidim-elections/spec/helpers/decidim/elections/steps_helper_spec.rb b/decidim-elections/spec/helpers/decidim/elections/steps_helper_spec.rb new file mode 100644 index 0000000000000..ec0074ba22f4a --- /dev/null +++ b/decidim-elections/spec/helpers/decidim/elections/steps_helper_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Elections + module Admin + describe StepsHelper do + describe "#steps" do + subject { helper.steps(current_step) } + + let(:current_step) { "create_election" } + + it { + expect(subject).to eq([ + ["create_election", "text-warning"], + ["key_ceremony", "text-muted"], + ["ready", "text-muted"], + ["vote", "text-muted"], + ["tally", "text-muted"], + ["results", "text-muted"], + ["results_published", "text-muted"] + ]) + } + + context "when current_step is ready" do + let(:current_step) { "ready" } + + it { + expect(subject).to eq([ + ["create_election", "text-success"], + ["key_ceremony", "text-success"], + ["ready", "text-warning"], + ["vote", "text-muted"], + ["tally", "text-muted"], + ["results", "text-muted"], + ["results_published", "text-muted"] + ]) + } + end + + context "when current_step is results_published" do + let(:current_step) { "results_published" } + + it { + expect(subject).to eq([ + ["create_election", "text-success"], + ["key_ceremony", "text-success"], + ["ready", "text-success"], + ["vote", "text-success"], + ["tally", "text-success"], + ["results", "text-success"], + ["results_published", "text-warning"] + ]) + } + end + end + end + end + end +end diff --git a/decidim-elections/spec/lib/query_extensions_spec.rb b/decidim-elections/spec/lib/query_extensions_spec.rb new file mode 100644 index 0000000000000..ba5f247d2bd13 --- /dev/null +++ b/decidim-elections/spec/lib/query_extensions_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/api/test/type_context" + +module Decidim + module Votings + describe Decidim::Api::QueryType do + include_context "with a graphql class type" + + describe "votings" do + let!(:voting1) { create(:voting, organization: current_organization) } + let!(:voting2) { create(:voting, organization: current_organization) } + let!(:voting3) { create(:voting) } + let!(:voting4) { create(:voting, :unpublished, organization: current_organization) } + + let(:query) { %({ votings { id }}) } + + it "returns all the votings" do + expect(response["votings"]).to include("id" => voting1.id.to_s) + expect(response["votings"]).to include("id" => voting2.id.to_s) + expect(response["votings"]).not_to include("id" => voting3.id.to_s) + expect(response["votings"]).not_to include("id" => voting4.id.to_s) + end + end + + describe "voting" do + let(:query) { %({ voting(id: \"#{id}\") { id }}) } + + context "with a voting that belongs to the current organization" do + let!(:voting) { create(:voting, organization: current_organization) } + let(:id) { voting.id } + + it "returns the voting" do + expect(response["voting"]).to eq("id" => voting.id.to_s) + end + end + + context "with a conference of another organization" do + let!(:voting) { create(:voting) } + let(:id) { voting.id } + + it "returns nil" do + expect(response["voting"]).to be_nil + end + end + end + end + end +end diff --git a/decidim-elections/spec/lib/tasks/decidim_election_scheduled_tasks_spec.rb b/decidim-elections/spec/lib/tasks/decidim_election_scheduled_tasks_spec.rb new file mode 100644 index 0000000000000..17a4f041fa9d0 --- /dev/null +++ b/decidim-elections/spec/lib/tasks/decidim_election_scheduled_tasks_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "decidim_elections:scheduled_tasks", type: :task do + it "runs gracefully" do + expect { task.execute }.not_to raise_error + end + + context "with elections to open", :vcr do + let!(:election) { create :election, :bb_test, :ready } + + before { task.execute } + + it "opens the Ballot Box" do + check_message_printed("Opening Election ##{election.id}:") + check_message_printed("Ballot Box opened. New bulletin board status: vote") + end + end + + context "with elections to close", :vcr do + let!(:election) { create :election, :bb_test, :vote, :finished } + + before { task.execute } + + it "closes the Ballot Box" do + check_message_printed("Closing Election ##{election.id}:") + check_message_printed("Ballot Box closed. New bulletin board status: tally") + end + end + + context "with elections that shouldn't be affected" do + let!(:election1) { create :election, :ready, start_time: 1.day.from_now } + let!(:election2) { create :election, :vote, :upcoming } + let!(:election3) { create :election, :vote, :ongoing } + + before { task.execute } + + it "don't modify them" do + expect(election1.reload).to be_bb_ready + expect(election2.reload).to be_bb_vote + end + end +end diff --git a/decidim-elections/spec/models/decidim/elections/election_spec.rb b/decidim-elections/spec/models/decidim/elections/election_spec.rb index e2fa73bd8c6a5..52bb9f3ee1e8b 100644 --- a/decidim-elections/spec/models/decidim/elections/election_spec.rb +++ b/decidim-elections/spec/models/decidim/elections/election_spec.rb @@ -42,4 +42,27 @@ it { is_expected.not_to be_ongoing } it { is_expected.to be_finished } end + + describe "start time checks" do + subject(:election) { build(:election, start_time: start_time) } + + let(:start_time) { 4.hours.from_now } + + it { is_expected.to be_minimum_hours_before_start } + it { is_expected.to be_maximum_hours_before_start } + + context "when the election is about to start" do + let(:start_time) { 1.hour.from_now } + + it { is_expected.not_to be_minimum_hours_before_start } + it { is_expected.to be_maximum_hours_before_start } + end + + context "when the election is not near to start" do + let(:start_time) { 10.days.from_now } + + it { is_expected.to be_minimum_hours_before_start } + it { is_expected.not_to be_maximum_hours_before_start } + end + end end diff --git a/decidim-elections/spec/models/decidim/elections/vote_spec.rb b/decidim-elections/spec/models/decidim/elections/vote_spec.rb new file mode 100644 index 0000000000000..7cf54cb73a533 --- /dev/null +++ b/decidim-elections/spec/models/decidim/elections/vote_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::Elections::Vote do + subject(:vote) { build(:vote) } + + it { is_expected.to be_valid } + + it "is invalid when the voter_id is not present" do + subject.voter_id = nil + expect(subject).to be_invalid + end + + it "is invalid when the election is not present" do + subject.election = nil + expect(subject).to be_invalid + end + + it "is invalid when encrypted_vote_hash is not present" do + subject.encrypted_vote_hash = nil + expect(subject).to be_invalid + end + + it "is invalid with a status that is not included in the allowed status" do + subject.status = "foo" + expect(subject).to be_invalid + end +end diff --git a/decidim-elections/spec/permissions/decidim/elections/admin/permissions_spec.rb b/decidim-elections/spec/permissions/decidim/elections/admin/permissions_spec.rb index 579dc6550785f..5611395d5847a 100644 --- a/decidim-elections/spec/permissions/decidim/elections/admin/permissions_spec.rb +++ b/decidim-elections/spec/permissions/decidim/elections/admin/permissions_spec.rb @@ -24,9 +24,9 @@ let(:questionnaire) { election&.questionnaire } let(:permission_action) { Decidim::PermissionAction.new(action) } - shared_examples "not allowed when election has started" do - context "when election has started" do - let(:election) { create :election, :started, component: elections_component } + shared_examples "not allowed when election was created on the bulletin board" do + context "when election was created on the bulletin board" do + let(:election) { create :election, :created, component: elections_component } it { is_expected.to eq false } end @@ -83,14 +83,6 @@ it { is_expected.to eq true } end - describe "election setup" do - let(:action) do - { scope: :admin, action: :setup, subject: :election } - end - - it { is_expected.to eq true } - end - describe "election update" do let(:action) do { scope: :admin, action: :update, subject: :election } @@ -98,7 +90,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "election publish" do @@ -109,7 +101,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" it_behaves_like "not allowed when election has invalid questions" end @@ -120,7 +112,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "election unpublish" do @@ -130,7 +122,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "questions" do @@ -144,7 +136,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "question update" do @@ -154,7 +146,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "question delete" do @@ -164,7 +156,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end end @@ -180,7 +172,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "answer update" do @@ -190,7 +182,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "answer delete" do @@ -200,7 +192,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "select answer" do @@ -220,7 +212,7 @@ it { is_expected.to eq true } - it_behaves_like "not allowed when election has started" + it_behaves_like "not allowed when election was created on the bulletin board" end describe "add user as trustee" do @@ -265,5 +257,21 @@ it { is_expected.to eq false } end end + + describe "read election steps" do + let(:action) do + { scope: :admin, action: :read, subject: :steps } + end + + it { is_expected.to eq true } + end + + describe "update election steps" do + let(:action) do + { scope: :admin, action: :update, subject: :steps } + end + + it { is_expected.to eq true } + end end end diff --git a/decidim-elections/spec/permissions/decidim/elections/trustee_zone/permissions_spec.rb b/decidim-elections/spec/permissions/decidim/elections/trustee_zone/permissions_spec.rb index 13bd4c9ec17f1..a2fc69e792e93 100644 --- a/decidim-elections/spec/permissions/decidim/elections/trustee_zone/permissions_spec.rb +++ b/decidim-elections/spec/permissions/decidim/elections/trustee_zone/permissions_spec.rb @@ -14,6 +14,10 @@ end let(:elections_component) { create :elections_component } let(:trustee) { create(:trustee, user: user) } + let(:election) do + create(:election, :ready_for_setup, trustees_participatory_space: trustee_participatory_space) + end + let(:trustee_participatory_space) { create :trustees_participatory_space, trustee: trustee } let(:permission_trustee) { trustee } let(:permission_action) { Decidim::PermissionAction.new(action) } @@ -33,6 +37,15 @@ end end + shared_examples "not allowed when election is not attached to trustee" do + context "when the election is not an election for the trustee" do + let(:permission_trustee) { create(:trustee) } + let(:permission_election) { create(:election, :ready_for_setup) } + + it { is_expected.to be_falsey } + end + end + context "when scope is not trustee zone" do let(:action) do { scope: :foo, action: :bar, subject: :election } @@ -78,4 +91,27 @@ it_behaves_like "not allowed when the user is not a trustee" it_behaves_like "not allowed when the given trustee is not the same than the user trustee" end + + describe "view election" do + let(:action) do + { scope: :trustee_zone, action: :view, subject: :election } + end + + it { is_expected.to eq true } + + it_behaves_like "not allowed when the user is not a trustee" + it_behaves_like "not allowed when the given trustee is not the same than the user trustee" + end + + describe "update election" do + let(:action) do + { scope: :trustee_zone, action: :update, subject: :election } + end + + it { is_expected.to eq true } + + it_behaves_like "not allowed when election is not attached to trustee" + it_behaves_like "not allowed when the user is not a trustee" + it_behaves_like "not allowed when the given trustee is not the same than the user trustee" + end end diff --git a/decidim-elections/spec/shared/vote_examples.rb b/decidim-elections/spec/shared/vote_examples.rb index 0e66c3eac8604..ff150aa044231 100644 --- a/decidim-elections/spec/shared/vote_examples.rb +++ b/decidim-elections/spec/shared/vote_examples.rb @@ -54,7 +54,29 @@ shared_examples "uses the voting booth" do include_context "with elections router" - it "uses the voting booth" do # rubocop:disable RSpec/ExampleLength + before do + proxy.stub("http://bulletin-board.lvh.me:8000/api", method: "options").and_return( + headers: { "Access-Control-Allow-Origin" => "*", + "Access-Control-Allow-Headers" => "content-type" }, + text: "" + ) + + proxy.stub("http://bulletin-board.lvh.me:8000/api", method: "post").and_return( + headers: { "Access-Control-Allow-Origin" => "*" }, + json: { + data: { + logEntry: { + messageId: "decidim-test-authority.10002.vote.cast+v.2b4ed424cb13184168b02ff54e0fc1d00ca666548007e5309c95faa92fe4e0f4", + signedData: "eyJhbGciOiJSUzI1NiJ9.eyJjb250ZW50Ijoie1wiYmFsbG90X3N0eWxlXCI6XCJ1bmlxdWVcIixcInF1ZXN0aW9uXzg2MVwiOltcIjU4MDhcIl0sXCJxdWVzdGlvbl84NjNcIjpbXCI1ODI0XCIsXCI1ODIxXCIsXCI1ODE5XCJdLFwicXVlc3Rpb25fODYyXCI6W1wiNTgxOFwiLFwiNTgxNVwiLFwiNTgxN1wiLFwiNTgxNFwiLFwiNTgxMlwiXX0iLCJpYXQiOjE2MTA1Njc4NDQsIm1lc3NhZ2VfaWQiOiJkZWNpZGltLXRlc3QtYXV0aG9yaXR5LjEwMDAyLnZvdGUuY2FzdCt2LjJiNGVkNDI0Y2IxMzE4NDE2OGIwMmZmNTRlMGZjMWQwMGNhNjY2NTQ4MDA3ZTUzMDljOTVmYWE5MmZlNGUwZjQifQ.eSnPvE3_Ty6IkTX-DFRGkH1tfBzpuFlqdgPo2EVz8laCznNN_xb_EwbMPHAtH0hrB0vT5slrPaM1HczUxylbrOIOwvziisccGe4Ey-mOaM56w8SicIJJjmx6E6f1LeRukoTkzvO2Qmn2U13Majzx0LPkkVTwIJw9rkgvIDfVre0l0qUn_yRlV2opJslmLPpjtBTZ-xvi35qu_J5C2GJzVAvb31CMgZUAKMLx75wyfqy2O0Els6C-jItMCR0xStfkNnI5QS5IkgA4L-rNLkOBEEuq2R4UJ4qiPHr2ZiFIUxP_g-Xd7fwRxQfzgt0dgxEVZYPE2DvtsAmaSEOT0VMymkjjsZzp7CtQf5dYEdXrpN4BRAf64gQ13MPh6NOnrLWlYNqlR7KhmxK4lr0LgosFdbD6xhXN8n37lNHz-u2CWs2XJgIDpEe9TOSlCj0GPpjV4jwRCrPUOtfx8_fUaxs2NhPrMTZGgshA7v_W4SGFZWb3ughPXV8XF5tauDnLTB2Ln-GKO9A4mQ-Wh1OXfUsdv_wLidtIOotzf62oujAHCJQHtlhMNsU-dLDpLlyxeHoaBY16j2pM46ufle7nCOMxg3TSZtfAuGoIjiAf9lRXyHRf5MLFPLVitO2-zV3xS4xPRL7WC7YvvpAGHK5Opq4xG_cnXht38VJrBIAuIbWL1hU", + contentHash: "128997c75969f759069b42c14c21e577d57e61f8314ab041847bfa994a28f483", + __typename: "LogEntry" + } + } + } + ) + end + + it "uses the voting booth", :vcr, :billy, :slow do selected_answers = [] non_selected_answers = [] @@ -116,23 +138,16 @@ # confirm step non_question_step("#step-4") do expect(page).to have_content("CONFIRM YOUR VOTE") - expect(page).to have_content("Blank") + selected_answers.each { |answer| expect(page).to have_i18n_content(answer.title) } non_selected_answers.each { |answer| expect(page).not_to have_i18n_content(answer.title) } click_link("Confirm") end - # ciphering animation step - non_question_step("#encrypting") do - expect(page).to have_content("Encoding vote...") - expect(page).to have_content("Your vote is being encrypted to ensure you can cast it anonymously.") - end - - sleep(3) - # confirmed vote page non_question_step("#confirmed_page") do + expect(page).to have_content("Processing vote") expect(page).to have_content("Vote confirmed") expect(page).to have_content("Your vote has already been cast!") end diff --git a/decidim-elections/spec/shared/voting_admin_shared_context.rb b/decidim-elections/spec/shared/voting_admin_shared_context.rb index 0e35e5f058a4f..7a9ddf85efd8d 100644 --- a/decidim-elections/spec/shared/voting_admin_shared_context.rb +++ b/decidim-elections/spec/shared/voting_admin_shared_context.rb @@ -8,6 +8,10 @@ let(:image1_path) { Decidim::Dev.asset(image1_filename) } let(:image2_filename) { "city2.jpeg" } let(:image2_path) { Decidim::Dev.asset(image2_filename) } + let(:image3_filename) { "city3.jpeg" } + let(:image3_path) { Decidim::Dev.asset(image3_filename) } + let(:image_invalid_filename) { "Exampledocument.pdf" } + let(:image_invalid_path) { Decidim::Dev.asset(image_invalid_filename) } let!(:voting) { create(:voting, organization: organization) } end diff --git a/decidim-elections/spec/spec_helper.rb b/decidim-elections/spec/spec_helper.rb index 0d108558a5cd9..ab7db6b3f600d 100644 --- a/decidim-elections/spec/spec_helper.rb +++ b/decidim-elections/spec/spec_helper.rb @@ -8,3 +8,12 @@ require "decidim/dev/test/base_spec_helper" require "decidim/forms/test" + +def in_browser(name) + old_session = Capybara.session_name + + Capybara.session_name = name + yield + + Capybara.session_name = old_session +end diff --git a/decidim-elections/spec/system/admin_manages_answers_spec.rb b/decidim-elections/spec/system/admin_manages_answers_spec.rb index 522b760576cad..cde702096ca0a 100644 --- a/decidim-elections/spec/system/admin_manages_answers_spec.rb +++ b/decidim-elections/spec/system/admin_manages_answers_spec.rb @@ -81,8 +81,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election was created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot add a new answer" do expect(page).to have_no_content("New Answer") @@ -124,8 +124,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election was created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot update the answer" do within find("tr", text: translated(answer.title)) do @@ -152,8 +152,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election was created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot delete the question" do within find("tr", text: translated(answer.title)) do diff --git a/decidim-elections/spec/system/admin_manages_election_steps_spec.rb b/decidim-elections/spec/system/admin_manages_election_steps_spec.rb new file mode 100644 index 0000000000000..4003496cb526a --- /dev/null +++ b/decidim-elections/spec/system/admin_manages_election_steps_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages election steps", type: :system do + let(:manifest_name) { "elections" } + + include_context "when managing a component as an admin" + + before do + election + switch_to_host(organization.host) + login_as user, scope: :user + visit_component_admin + end + + describe "setup an election", :vcr do + let!(:election) { create :election, :ready_for_setup, component: current_component } + + it "performs the action successfully" do + within find("tr", text: translated(election.title)) do + page.find(".action-icon--manage-steps").click + end + + within "form.create_election" do + expect(page).to have_content("The election has at least 1 question.") + expect(page).to have_content("Each question has at least 2 answers.") + expect(page).to have_content("All the questions have a correct value for maximum of answers.") + expect(page).to have_content("The election is published.") + expect(page).to have_content("The setup is being done at least 3 hours before the election starts.") + expect(page).to have_content("The participatory space has at least 2 trustees with public key.") + expect(page).to have_content("has a public key", minimum: 2) + + click_button "Setup election" + end + + expect(page).to have_admin_callout("successfully") + + within ".content.key_ceremony" do + expect(page).to have_content("Key ceremony") + end + end + end + + describe "view key ceremony step" do + let!(:election) { create :election, :created, component: current_component } + + it "shows the step information" do + within find("tr", text: translated(election.title)) do + page.find(".action-icon--manage-steps").click + end + + within ".content.key_ceremony" do + expect(page).to have_content("Key ceremony") + end + end + end + + describe "open the ballot box", :vcr do + let!(:election) { create :election, :bb_test, :ready, component: current_component } + + it "performs the action successfully" do + within find("tr", text: translated(election.title)) do + page.find(".action-icon--manage-steps").click + end + + within "form.ready" do + expect(page).to have_content("The election will start soon.") + + click_button "Open ballot box" + end + + expect(page).to have_admin_callout("successfully") + + within "form.vote" do + expect(page).to have_content("Vote period") + end + end + end + + describe "close the ballot box", :vcr do + let!(:election) { create :election, :bb_test, :vote, :finished, component: current_component } + + it "performs the action successfully" do + within find("tr", text: translated(election.title)) do + page.find(".action-icon--manage-steps").click + end + + within "form.vote" do + expect(page).to have_content("The election has ended.") + + click_button "Close ballot box" + end + + expect(page).to have_admin_callout("successfully") + + within ".content.tally" do + expect(page).to have_content("Tally") + end + end + end +end diff --git a/decidim-elections/spec/system/admin_manages_elections_spec.rb b/decidim-elections/spec/system/admin_manages_elections_spec.rb index f40524df24553..45299701d17f7 100644 --- a/decidim-elections/spec/system/admin_manages_elections_spec.rb +++ b/decidim-elections/spec/system/admin_manages_elections_spec.rb @@ -130,34 +130,6 @@ end end - describe "set up an election" do - context "when the election is published", :vcr do - let!(:election) { create :election, :published, :ready_for_setup, component: current_component } - - it "sets up an election" do - within find("tr", text: translated(election.title)) do - page.find(".action-icon--setup-election").click - end - - within ".setup_election" do - expect(page).to have_css(".card-title", text: "Election setup") - expect(page).to have_content("The election is published") - expect(page).to have_content("The setup is being done at least 3 hours before the election starts") - expect(page).to have_content("The election has at least 1 question") - expect(page).to have_content("Each question has at least 2 answers") - expect(page).to have_content("All the questions have a correct value for maximum of answers") - expect(page).to have_content("The size of this list of trustees is correct and it will be needed at least #{Decidim::Elections.bulletin_board.quorum} trustees to perform the tally process") - Decidim::Elections.bulletin_board.quorum.times do - expect(page).to have_content("valid public key") - end - - page.find(".button").click - end - expect(page).to have_admin_callout("successfully") - end - end - end - describe "unpublishing an election" do let!(:election) { create :election, :published, :ready_for_setup, component: current_component } @@ -215,8 +187,8 @@ end end - context "when the election has started" do - let!(:election) { create(:election, :started, component: current_component) } + context "when the election has created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot delete the election" do within find("tr", text: translated(election.title)) do diff --git a/decidim-elections/spec/system/admin_manages_questions_spec.rb b/decidim-elections/spec/system/admin_manages_questions_spec.rb index ac24768c7a5c5..ef4b06cafd4b4 100644 --- a/decidim-elections/spec/system/admin_manages_questions_spec.rb +++ b/decidim-elections/spec/system/admin_manages_questions_spec.rb @@ -59,8 +59,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election has created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot add a new question" do expect(page).to have_no_content("New Question") @@ -94,8 +94,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election has created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot update the question" do within find("tr", text: translated(question.title)) do @@ -122,8 +122,8 @@ end end - context "when the election has started" do - let(:election) { create(:election, :started, component: current_component) } + context "when the election has created on the bulletin board" do + let(:election) { create(:election, :created, component: current_component) } it "cannot delete the question" do within find("tr", text: translated(question.title)) do diff --git a/decidim-elections/spec/system/admin_manages_votings_spec.rb b/decidim-elections/spec/system/admin_manages_votings_spec.rb index ceece8a991aa8..2f1f6688a6dba 100644 --- a/decidim-elections/spec/system/admin_manages_votings_spec.rb +++ b/decidim-elections/spec/system/admin_manages_votings_spec.rb @@ -112,4 +112,163 @@ expect(page).to have_admin_callout("problem") end end + + describe "updating a voting" do + before do + click_link translated(voting.title) + end + + it "updates a voting" do + fill_in_i18n( + :voting_title, + "#voting-title-tabs", + en: "My new title", + es: "Mi nuevo título", + ca: "El meu nou títol" + ) + attach_file :voting_banner_image, image3_path + + within ".edit_voting" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + + within ".container" do + expect(page).to have_selector("input[value='My new title']") + expect(page).not_to have_css("img[src*='#{image2_filename}']") + expect(page).to have_css("img[src*='#{image3_filename}']") + end + end + end + + describe "updating a voting with invalid values" do + before do + click_link translated(voting.title) + end + + it "does not update the voting" do + fill_in_i18n( + :voting_title, + "#voting-title-tabs", + en: "", + es: "", + ca: "" + ) + + within ".edit_voting" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("problem") + end + end + + describe "updating a voting with invalid image" do + before do + click_link translated(voting.title) + end + + it "does not update the voting" do + attach_file :voting_banner_image, image_invalid_path + + within ".edit_voting" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("problem") + end + end + + describe "updating a voting without images" do + let!(:voting3) { create(:voting, organization: organization) } + + before do + visit decidim_admin_votings.votings_path + end + + it "does not delete them" do + click_link translated(voting3.title) + + within ".edit_voting" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + expect(page).to have_css("img[src*='#{voting3.banner_image.url}']") + end + end + + describe "previewing votings" do + let!(:voting) { create(:voting, :unpublished, organization: organization) } + + it "allows the user to preview the unpublished voting" do + within find("tr", text: translated(voting.title)) do + preview_window = window_opened_by do + click_link "Preview" + end + + within_window(preview_window) do + expect(page).to have_i18n_content(voting.title) + expect(page).to have_i18n_content(voting.description) + end + end + end + end + + describe "viewing a missing voting" do + it_behaves_like "a 404 page" do + let(:target_path) { decidim_admin_votings.voting_path(99_999_999) } + end + end + + describe "publishing a voting" do + let!(:voting) { create(:voting, :unpublished, organization: organization) } + + before do + click_link translated(voting.title) + end + + it "publishes the voting" do + click_link "Publish" + expect(page).to have_content("successfully published") + expect(page).to have_content("Unpublish") + expect(page).to have_current_path decidim_admin_votings.edit_voting_path(voting) + + voting.reload + expect(voting).to be_published + end + end + + describe "unpublishing a voting" do + let!(:voting) { create(:voting, :published, organization: organization) } + + before do + click_link translated(voting.title) + end + + it "unpublishes the voting" do + click_link "Unpublish" + expect(page).to have_content("successfully unpublished") + expect(page).to have_content("Publish") + expect(page).to have_current_path decidim_admin_votings.edit_voting_path(voting) + + voting.reload + expect(voting).not_to be_published + end + end + + context "when there are multiple organizations in the system" do + let!(:external_voting) { create(:voting) } + + before do + visit decidim_admin_votings.votings_path + end + + it "doesn't let the admin manage assemblies form other organizations" do + within "table" do + expect(page).not_to have_content(external_voting.title["en"]) + end + end + end end diff --git a/decidim-elections/spec/system/key_ceremony_spec.rb b/decidim-elections/spec/system/key_ceremony_spec.rb new file mode 100644 index 0000000000000..ccc603465b48b --- /dev/null +++ b/decidim-elections/spec/system/key_ceremony_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Key ceremony", type: :system do + let!(:election) { create :election, :ready_for_setup, id: 42, trustee_keys: trustee_keys, component: current_component } + + let(:manifest_name) { "elections" } + let(:trustee_keys) do + { + "Trustee 1" => File.read(Decidim::Dev.asset("public_key.jwk")), + "Trustee 2" => File.read(Decidim::Dev.asset("public_key2.jwk")) + } + end + let(:private_keys) do + [ + Decidim::Dev.asset("private_key.jwk"), + Decidim::Dev.asset("private_key2.jwk") + ] + end + + describe "key ceremony process" do + include_context "when managing a component as an admin" do + let(:admin_component_organization_traits) { [:secure_context] } + end + + context "when performing the key ceremony", :vcr, :billy, :slow, download: true do + it "generates backup keys, restores them and creates election keys" do + setup_election(election) + + proxy.cache.with_scope("trustee 1 download") { download_election_keys(0) } + proxy.cache.with_scope("trustee 2 download") { download_election_keys(1) } + + proxy.cache.with_scope("complete ceremony with trustee 1") { complete_key_ceremony(0) } + proxy.cache.with_scope("check complete ceremony with trustee 2") { check_key_ceremony_completed(1) } + end + end + + def setup_election(election) + login_as user, scope: :user + visit_component_admin + + within find("tr", text: translated(election.title)) do + page.find(".action-icon--manage-steps").click + end + + click_button "Setup election" + + election.reload + end + + def download_election_keys(trustee_index) + trustee = access_trustee_zone(trustee_index) + + perform_key_ceremony_action + + click_button "Download keys" + + content = download_content("#{trustee.unique_id}-*.bak") + expect(content).to have_content(%(trusteeId":"#{trustee.unique_id})) + expect(content).to have_content('"status":"key_ceremony.step_1"') + end + + def access_trustee_zone(trustee_index, upload_keys = true) # rubocop:disable Style/OptionalBooleanParameter + trustee = election.trustees[trustee_index] + + relogin_as trustee.user, scope: :user + visit decidim.decidim_elections_trustee_zone_path + + if upload_keys + attach_file(private_keys[trustee_index]) do + click_button "Upload your identification keys" + end + end + + expect(page).not_to have_content("Upload your identification keys") + + trustee + end + + def perform_key_ceremony_action + expect(page).to have_content("Elections") + + click_link "Perform action" + + expect(page).to have_content("Create election keys") + expect(page).to have_css("#create_election", text: "Pending") + expect(page).to have_css("#key_ceremony-step_1", text: "Pending") + expect(page).to have_css("#key_ceremony-joint_election_key", text: "Pending") + + expect(page).to have_selector("button.start:not(disabled)") + + sleep(1) + + click_button "Start" + + expect(page).to have_selector("button.start:disabled") + end + + def complete_key_ceremony(trustee_index) + trustee = access_trustee_zone(trustee_index, false) + + perform_key_ceremony_action + + expect(page).to have_content("Restore election keys for #{translated(election.title, locale: :en)}") + + attach_file(download_path("#{trustee.unique_id}-*.bak")) do + click_button "Upload election keys" + end + + expect(page).to have_css("#create_election", text: "Completed") + expect(page).to have_css("#key_ceremony-step_1", text: "Completed") + expect(page).to have_css("#key_ceremony-joint_election_key", text: "Completed") + expect(page).not_to have_selector("button.start") + expect(page).to have_link("Back") + + expect(page).to have_content("The election status is: ready") + end + + def check_key_ceremony_completed(trustee_index) + access_trustee_zone(trustee_index, false) + + expect(page).to have_content("Elections") + + within ".trustee_zone table" do + expect(page).to have_content(translated(election.title, locale: :en)) + expect(page).to have_content("ready") + expect(page).not_to have_link("Perform action") + end + end + end +end diff --git a/decidim-elections/spec/system/vote_spec.rb b/decidim-elections/spec/system/vote_spec.rb index 15e41a8106ab1..369741c08ffe4 100644 --- a/decidim-elections/spec/system/vote_spec.rb +++ b/decidim-elections/spec/system/vote_spec.rb @@ -4,19 +4,20 @@ describe "Vote in an election", type: :system do let(:manifest_name) { "elections" } - let(:election) { create :election, :complete, :published, :ongoing, component: component } - let(:user) { create(:user, :confirmed, organization: component.organization) } + let(:election) { create :election, :bb_test, :vote, component: component } + let(:user) { create(:user, :confirmed, created_at: Date.civil(2020, 1, 1), organization: component.organization) } let!(:elections) do - create_list(:election, 2, :complete, :published, :ongoing, component: component) + create_list(:election, 2, :vote, component: component) end before do election.reload - switch_to_host(organization.host) login_as user, scope: :user end - include_context "with a component" + include_context "with a component" do + let(:organization_traits) { [:secure_context] } + end it_behaves_like "allows to vote" @@ -97,17 +98,6 @@ it_behaves_like "allow admins to preview the voting booth" end - context "when the voting is confirmed" do - before do - visit_component - - click_link translated(election.title) - click_link "Vote" - end - - it_behaves_like "uses the voting booth" - end - context "when the voting is not confirmed" do it "is alerted when trying to leave the component before completing" do visit_component diff --git a/decidim-elections/spec/system/voting_spec.rb b/decidim-elections/spec/system/voting_spec.rb new file mode 100644 index 0000000000000..43470a01e8295 --- /dev/null +++ b/decidim-elections/spec/system/voting_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Voting", type: :system do + let!(:organization) { create(:organization) } + let!(:voting) { create(:voting, :published, organization: organization) } + let!(:user) { create :user, :confirmed, organization: organization } + + before do + switch_to_host(organization.host) + end + + it_behaves_like "editable content for admins" do + let(:target_path) { decidim_votings.voting_path(voting) } + end + + context "when requesting the voting path" do + before do + visit decidim_votings.voting_path(voting) + end + + it "shows the basic voting data" do + expect(page).to have_i18n_content(voting.title) + expect(page).to have_i18n_content(voting.description) + end + + context "when the voting is unpublished" do + let!(:voting) do + create(:voting, :unpublished, organization: organization) + end + + before do + switch_to_host(organization.host) + visit decidim_votings.voting_path(voting) + end + + it "redirects to root path" do + expect(page).to have_current_path("/") + end + end + end +end diff --git a/decidim-elections/spec/system/votings_spec.rb b/decidim-elections/spec/system/votings_spec.rb new file mode 100644 index 0000000000000..9f565cf135def --- /dev/null +++ b/decidim-elections/spec/system/votings_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/core/test/shared_examples/has_contextual_help" + +describe "Votings", type: :system do + let(:organization) { create(:organization) } + + before do + switch_to_host(organization.host) + end + + it_behaves_like "shows contextual help" do + let(:index_path) { decidim_votings.votings_path } + let(:manifest_name) { :votings } + end + + context "when ordering by 'Most recent'" do + let!(:older_voting) do + create(:voting, :published, organization: organization, created_at: 1.month.ago) + end + + let!(:recent_voting) do + create(:voting, :published, organization: organization, created_at: Time.now.utc) + end + + before do + switch_to_host(organization.host) + end + + it_behaves_like "editable content for admins" do + let(:target_path) { visit decidim_votings.votings_path } + end + + context "when requesting the votings path" do + before do + visit decidim_votings.votings_path + end + + it "lists the votings ordered by created at" do + within ".order-by" do + expect(page).to have_selector("ul[data-dropdown-menu$=dropdown-menu]", text: "Random") + page.find("a", text: "Random").click + click_link "Most recent" + end + + expect(page).to have_selector("#votings .card-grid .column:first-child", text: recent_voting.title[:en]) + expect(page).to have_selector("#votings .card-grid .column:last-child", text: older_voting.title[:en]) + end + end + end + + context "when ordering by 'Random'" do + let!(:votings) { create_list(:voting, 2, :published, organization: organization) } + + before do + switch_to_host(organization.host) + visit decidim_votings.votings_path + end + + it "shows all votings" do + within ".order-by" do + expect(page).to have_selector("ul[data-dropdown-menu$=dropdown-menu]", text: "Random") + end + + expect(page).to have_selector(".card--voting", count: 2) + expect(page).to have_content(translated(votings.first.title)) + expect(page).to have_content(translated(votings.last.title)) + end + end +end diff --git a/decidim-elections/spec/types/decidim/elections/election_type_spec.rb b/decidim-elections/spec/types/decidim/elections/election_type_spec.rb index 592e5ad0b043c..37340f8aac082 100644 --- a/decidim-elections/spec/types/decidim/elections/election_type_spec.rb +++ b/decidim-elections/spec/types/decidim/elections/election_type_spec.rb @@ -70,7 +70,7 @@ module Elections let(:query) { "{ blocked }" } context "when the election's parameters are blocked" do - let!(:model) { create(:election, :started, :ready_for_setup) } + let!(:model) { create(:election, :created) } it "returns true " do expect(response["blocked"]).to be true diff --git a/decidim-forms/app/commands/decidim/forms/answer_questionnaire.rb b/decidim-forms/app/commands/decidim/forms/answer_questionnaire.rb index f85f1f635ed17..5dfae543a55fb 100644 --- a/decidim-forms/app/commands/decidim/forms/answer_questionnaire.rb +++ b/decidim-forms/app/commands/decidim/forms/answer_questionnaire.rb @@ -4,6 +4,8 @@ module Decidim module Forms # This command is executed when the user answers a Questionnaire. class AnswerQuestionnaire < Rectify::Command + include ::Decidim::MultipleAttachmentsMethods + # Initializes a AnswerQuestionnaire Command. # # form - The form from which to get the data. @@ -21,14 +23,34 @@ def call return broadcast(:invalid) if @form.invalid? answer_questionnaire - broadcast(:ok) + + if @errors + reset_form_attachments + broadcast(:invalid) + else + broadcast(:ok) + end end attr_reader :form private + # This method will add an error to the `add_documents` field only if there's + # any error in any other field or an error in another answer in the + # questionnaire. This is needed because when the form has + # an error, the attachments are lost, so we need a way to inform the user + # of this problem. + def reset_form_attachments + @form.responses.each do |answer| + answer.errors.add(:add_documents, :needs_to_be_reattached) if answer.has_attachments? + end + end + def answer_questionnaire + @main_form = @form + @errors = nil + Answer.transaction do form.responses_by_step.flatten.select(&:display_conditions_fulfilled?).each do |form_answer| answer = Answer.new( @@ -51,7 +73,27 @@ def answer_questionnaire end answer.save! + + next unless form_answer.question.has_attachments? + + # The attachments module expects `@form` to be the form with the + # attachments + @form = form_answer + @attached_to = answer + + build_attachments + + if attachments_invalid? + @errors = true + next + end + + create_attachments if process_attachments? + document_cleanup! end + + @form = @main_form + raise ActiveRecord::Rollback if @errors end end end diff --git a/decidim-forms/app/forms/decidim/forms/answer_form.rb b/decidim-forms/app/forms/decidim/forms/answer_form.rb index a3ff037badf38..223f894be3276 100644 --- a/decidim-forms/app/forms/decidim/forms/answer_form.rb +++ b/decidim-forms/app/forms/decidim/forms/answer_form.rb @@ -10,6 +10,8 @@ class AnswerForm < Decidim::Form attribute :body, String attribute :choices, Array[AnswerChoiceForm] attribute :matrix_choices, Array[AnswerChoiceForm] + attribute :documents, Array[String] + attribute :add_documents, Array validates :body, presence: true, if: :mandatory_body? validates :selected_choices, presence: true, if: :mandatory_choices? @@ -17,6 +19,7 @@ class AnswerForm < Decidim::Form validate :max_choices, if: -> { question.max_choices } validate :all_choices, if: -> { question.question_type == "sorting" } validate :min_choices, if: -> { question.matrix? && question.mandatory? } + validate :documents_present, if: -> { question.question_type == "files" && question.mandatory? } delegate :mandatory_body?, :mandatory_choices?, :matrix?, to: :question @@ -56,6 +59,10 @@ def display_conditions_fulfilled? end end + def has_attachments? + question.has_attachments? && errors[:add_documents].empty? && add_documents.present? + end + private def mandatory_body? @@ -93,6 +100,10 @@ def mandatory_label def max_choices_label I18n.t("questionnaires.question.max_choices", scope: "decidim.forms", n: question.max_choices) end + + def documents_present + errors.add(:add_documents, :blank) if add_documents.empty? && errors[:add_documents].empty? + end end end end diff --git a/decidim-forms/app/models/decidim/forms/answer.rb b/decidim-forms/app/models/decidim/forms/answer.rb index 769a12fb03a32..e96206fd6de1f 100644 --- a/decidim-forms/app/models/decidim/forms/answer.rb +++ b/decidim-forms/app/models/decidim/forms/answer.rb @@ -6,6 +6,7 @@ module Forms class Answer < Forms::ApplicationRecord include Decidim::DataPortability include Decidim::NewsletterParticipant + include Decidim::HasAttachments belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id", optional: true belongs_to :questionnaire, class_name: "Questionnaire", foreign_key: "decidim_questionnaire_id" @@ -39,6 +40,11 @@ def self.newsletter_participant_ids(component) answers.pluck(:decidim_user_id).flatten.compact.uniq end + def organization + user.organization if user.present? + questionnaire&.questionnaire_for.try(:organization) + end + private def user_questionnaire_same_organization diff --git a/decidim-forms/app/models/decidim/forms/question.rb b/decidim-forms/app/models/decidim/forms/question.rb index 42c5570d29c44..48212f3453080 100644 --- a/decidim-forms/app/models/decidim/forms/question.rb +++ b/decidim-forms/app/models/decidim/forms/question.rb @@ -6,7 +6,7 @@ module Forms class Question < Forms::ApplicationRecord include Decidim::TranslatableResource - QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting matrix_single matrix_multiple).freeze + QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting files matrix_single matrix_multiple).freeze SEPARATOR_TYPE = "separator" TYPES = (QUESTION_TYPES + [SEPARATOR_TYPE]).freeze @@ -63,11 +63,11 @@ def multiple_choice? end def mandatory_body? - mandatory? && !multiple_choice? + mandatory? && !multiple_choice? && !has_attachments? end def mandatory_choices? - mandatory? && multiple_choice? + mandatory? && multiple_choice? && !has_attachments? end def number_of_options @@ -81,6 +81,10 @@ def translated_body def separator? question_type.to_s == SEPARATOR_TYPE end + + def has_attachments? + question_type.to_s == "files" + end end end end diff --git a/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb b/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb index f4462b5caa69c..2afb57f3faa11 100644 --- a/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +++ b/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb @@ -17,6 +17,7 @@ def question def body return answer.body if answer.body.present? + return attachments if answer.attachments.any? return "-" if answer.choices.empty? choices = answer.choices.map do |choice| @@ -30,8 +31,27 @@ def body end end + def attachments + content_tag(:ul) do + safe_join(answer.attachments.map { |a| pretty_attachment(a) }) + end + end + private + def pretty_attachment(attachment) + # rubocop:disable Style/StringConcatenation + # Interpolating strings that are `html_safe` is problematic with Rails. + content_tag :li do + link_to(translated_attribute(attachment.title), attachment.url) + + " " + + content_tag(:small) do + "#{attachment.file_type} #{number_to_human_size(attachment.file_size)}" + end + end + # rubocop:enable Style/StringConcatenation + end + def choice(choice_body) content_tag :li do choice_body diff --git a/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb b/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb index 93af1014be3e4..87eab4ff7de46 100644 --- a/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +++ b/decidim-forms/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb @@ -36,12 +36,12 @@ def answers end def first_short_answer - short = sibilings.where("decidim_forms_questions.question_type in (?)", %w(short_answer)) + short = sibilings.where(decidim_forms_questions: { question_type: %w(short_answer) }) short.first end def completion - with_body = sibilings.where("decidim_forms_questions.question_type in (?)", %w(short_answer long_answer)) + with_body = sibilings.where(decidim_forms_questions: { question_type: %w(short_answer long_answer) }) .where.not(body: "").count with_choices = sibilings.where.not("decidim_forms_questions.question_type in (?)", %w(short_answer long_answer)) .where("decidim_forms_answers.id IN (SELECT decidim_answer_id FROM decidim_forms_answer_choices)").count diff --git a/decidim-forms/app/views/decidim/forms/questionnaires/answers/_files.html.erb b/decidim-forms/app/views/decidim/forms/questionnaires/answers/_files.html.erb new file mode 100644 index 0000000000000..3d6005a1d8a48 --- /dev/null +++ b/decidim-forms/app/views/decidim/forms/questionnaires/answers/_files.html.erb @@ -0,0 +1 @@ +<%= answer_form.file_field :add_documents, label: false, multiple: true, id: field_id, disabled: disabled %> diff --git a/decidim-forms/app/views/decidim/forms/questionnaires/show.html.erb b/decidim-forms/app/views/decidim/forms/questionnaires/show.html.erb index b096f2d6f1b7b..9b08247211d65 100644 --- a/decidim-forms/app/views/decidim/forms/questionnaires/show.html.erb +++ b/decidim-forms/app/views/decidim/forms/questionnaires/show.html.erb @@ -56,7 +56,7 @@
<% end %> - <%= decidim_form_for(@form, url: update_url, method: :post, html: { class: "form answer-questionnaire" }, data: { "safe-path" => form_path }) do |form| %> + <%= decidim_form_for(@form, url: update_url, method: :post, html: { multipart: true, class: "form answer-questionnaire" }, data: { "safe-path" => form_path }) do |form| %> <%= form_required_explanation %> <%= invisible_captcha %> <% answer_idx = 0 %> diff --git a/decidim-forms/config/locales/en.yml b/decidim-forms/config/locales/en.yml index 6c1b46f1f3e77..118ff0ca7140c 100644 --- a/decidim-forms/config/locales/en.yml +++ b/decidim-forms/config/locales/en.yml @@ -15,6 +15,8 @@ en: models: answer: attributes: + add_documents: + needs_to_be_reattached: Needs to be reattached choices: missing: are not complete too_many: are too many @@ -119,6 +121,7 @@ en: resize_and_pad: Resized and padded to resize_to_fit: Resized to fit question_types: + files: Files long_answer: Long answer matrix_multiple: Matrix (Multiple option) matrix_single: Matrix (Single option) diff --git a/decidim-forms/lib/decidim/forms/data_portability_user_answers_serializer.rb b/decidim-forms/lib/decidim/forms/data_portability_user_answers_serializer.rb index b81f1a590f550..6f4be45a74518 100644 --- a/decidim-forms/lib/decidim/forms/data_portability_user_answers_serializer.rb +++ b/decidim-forms/lib/decidim/forms/data_portability_user_answers_serializer.rb @@ -30,7 +30,13 @@ def serialize private def normalize_body(resource) - resource.body || resource.choices.pluck(:body) + attachments_for(resource) || resource.body || resource.choices.pluck(:body) + end + + def attachments_for(resource) + return if resource.attachments.blank? + + resource.attachments.map(&:url) end end end diff --git a/decidim-forms/lib/decidim/forms/test/factories.rb b/decidim-forms/lib/decidim/forms/test/factories.rb index 29e10925db8ef..39bf60ae62966 100644 --- a/decidim-forms/lib/decidim/forms/test/factories.rb +++ b/decidim-forms/lib/decidim/forms/test/factories.rb @@ -91,6 +91,13 @@ question { create(:questionnaire_question, questionnaire: questionnaire) } user { create(:user, organization: questionnaire.questionnaire_for.organization) } session_token { Digest::MD5.hexdigest(user.id.to_s) } + + trait :with_attachments do + after(:create) do |answer, _evaluator| + create :attachment, :with_image, attached_to: answer + create :attachment, :with_pdf, attached_to: answer + end + end end factory :answer_option, class: "Decidim::Forms::AnswerOption" do diff --git a/decidim-forms/lib/decidim/forms/user_answers_serializer.rb b/decidim-forms/lib/decidim/forms/user_answers_serializer.rb index f9f8d1908faf1..73a5419d093ec 100644 --- a/decidim-forms/lib/decidim/forms/user_answers_serializer.rb +++ b/decidim-forms/lib/decidim/forms/user_answers_serializer.rb @@ -28,7 +28,15 @@ def serialize private def normalize_body(answer) - answer.body || normalize_choices(answer, answer.choices) + answer.body || + normalize_attachments(answer) || + normalize_choices(answer, answer.choices) + end + + def normalize_attachments(answer) + return if answer.attachments.blank? + + answer.attachments.map(&:url) end def normalize_choices(answer, choices) diff --git a/decidim-forms/spec/commands/decidim/forms/answer_questionnaire_spec.rb b/decidim-forms/spec/commands/decidim/forms/answer_questionnaire_spec.rb index 2394c24b3dce8..ae798159453e1 100644 --- a/decidim-forms/spec/commands/decidim/forms/answer_questionnaire_spec.rb +++ b/decidim-forms/spec/commands/decidim/forms/answer_questionnaire_spec.rb @@ -123,6 +123,53 @@ def tokenize(id) expect(Answer.third.ip_hash).to eq(nil) end end + + context "with attachments" do + let(:question_1) { create(:questionnaire_question, questionnaire: questionnaire, question_type: :files) } + let(:form_params) do + { + "responses" => [ + { + "add_documents" => uploaded_files, + "question_id" => question_1.id + } + ], + "tos_agreement" => "1" + } + end + + context "when attachments are allowed", processing_uploads_for: Decidim::AttachmentUploader do + let(:uploaded_files) do + [ + Decidim::Dev.test_file("city.jpeg", "image/jpeg"), + Decidim::Dev.test_file("Exampledocument.pdf", "application/pdf") + ] + end + + it "creates multiple atachments for the proposal" do + expect { command.call }.to change(Decidim::Attachment, :count).by(2) + last_attachment = Decidim::Attachment.last + expect(last_attachment.attached_to).to be_kind_of(Decidim::Forms::Answer) + end + end + + context "when attachments are allowed and file is invalid", processing_uploads_for: Decidim::AttachmentUploader do + let(:uploaded_files) do + [ + Decidim::Dev.test_file("city.jpeg", "image/jpeg"), + Decidim::Dev.test_file("Exampledocument.pdf", "") + ] + end + + it "does not create atachments for the proposal" do + expect { command.call }.to change(Decidim::Attachment, :count).by(0) + end + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + end + end end describe "when the user is unregistered" do diff --git a/decidim-forms/spec/forms/decidim/forms/answer_form_spec.rb b/decidim-forms/spec/forms/decidim/forms/answer_form_spec.rb index 8d733c757b708..e0037f36659db 100644 --- a/decidim-forms/spec/forms/decidim/forms/answer_form_spec.rb +++ b/decidim-forms/spec/forms/decidim/forms/answer_form_spec.rb @@ -68,6 +68,37 @@ module Forms end end + context "and question type is files" do + let(:question_type) { "files" } + let(:uploaded_files) do + [ + Decidim::Dev.test_file("city.jpeg", "image/jpeg"), + Decidim::Dev.test_file("Exampledocument.pdf", "application/pdf") + ] + end + + context "when the body is empty" do + before do + subject.body = nil + subject.add_documents = uploaded_files + end + + it "is valid" do + expect(subject).to be_valid + end + end + + context "when there are no uploaded files" do + before do + subject.add_documents = nil + end + + it "is not valid if there are no files" do + expect(subject).not_to be_valid + end + end + end + context "and question has display conditions" do let(:question_type) { "short_answer" } let!(:condition_question) { create(:questionnaire_question, questionnaire: questionnaire, question_type: question_type) } diff --git a/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb b/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb index a536295591dcb..b8422db4edbd5 100644 --- a/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb +++ b/decidim-forms/spec/serializers/decidim/forms/user_answers_serializer_spec.rb @@ -55,6 +55,11 @@ module Forms end.flatten end + let!(:files_question) { create :questionnaire_question, questionnaire: questionnaire, question_type: "files" } + let!(:files_answer) do + create :answer, :with_attachments, questionnaire: questionnaire, question: files_question, user: user, body: nil + end + describe "#serialize" do let(:serialized) { subject.serialize } @@ -74,6 +79,8 @@ module Forms [key, choices.map { |choice| choice&.body }] end.to_h + serialized_files_answer = files_answer.attachments.map(&:url) + expect(serialized).to include( "4. #{translated(multichoice_question.body, locale: I18n.locale)}" => multichoice_answer_choices.map(&:body) ) @@ -85,6 +92,10 @@ module Forms expect(serialized).to include( "6. #{translated(matrixmultiple_question.body, locale: I18n.locale)}" => serialized_matrix_answer ) + + expect(serialized).to include( + "7. #{translated(files_question.body, locale: I18n.locale)}" => serialized_files_answer + ) end context "and includes the attributes" do diff --git a/decidim-forms/spec/services/decidim/forms/data_portability_user_answer_serializer_spec.rb b/decidim-forms/spec/services/decidim/forms/data_portability_user_answer_serializer_spec.rb index be08619c2f5f9..b4f2e4143deb2 100644 --- a/decidim-forms/spec/services/decidim/forms/data_portability_user_answer_serializer_spec.rb +++ b/decidim-forms/spec/services/decidim/forms/data_portability_user_answer_serializer_spec.rb @@ -17,6 +17,55 @@ module Forms describe "#serialize" do let(:serialized) { subject.serialize } + context "when question is files" do + let!(:question) { create :questionnaire_question, questionnaire: questionnaire, question_type: :files } + let!(:answer) { create :answer, :with_attachments, questionnaire: questionnaire, question: question, user: user } + + it "includes the answer id" do + expect(serialized).to include(id: answer.id) + end + + it "includes the user" do + expect(serialized[:user]).to( + include(name: answer.user.name) + ) + expect(serialized[:user]).to( + include(email: answer.user.email) + ) + end + + it "includes the questionnaire information" do + expect(serialized[:questionnaire]).to( + include(id: questionnaire.id) + ) + expect(serialized[:questionnaire]).to( + include(title: translated_attribute(questionnaire.title)) + ) + expect(serialized[:questionnaire]).to( + include(description: translated_attribute(questionnaire.description)) + ) + expect(serialized[:questionnaire]).to( + include(tos: translated_attribute(questionnaire.tos)) + ) + end + + it "includes the question info" do + expect(serialized[:question]).to( + include(id: question.id) + ) + expect(serialized[:question]).to( + include(body: translated_attribute(question.body)) + ) + expect(serialized[:question]).to( + include(description: translated_attribute(question.description)) + ) + end + + it "includes the answer " do + expect(serialized).to include(answer: answer.attachments.map(&:url)) + end + end + context "when question is shortanswer" do let!(:question) { create :questionnaire_question, questionnaire: questionnaire } let!(:answer) { create :answer, questionnaire: questionnaire, question: question, user: user } diff --git a/decidim-generators/Gemfile.lock b/decidim-generators/Gemfile.lock index a11d288b7c179..6e7002d99feaa 100644 --- a/decidim-generators/Gemfile.lock +++ b/decidim-generators/Gemfile.lock @@ -142,6 +142,7 @@ PATH i18n-tasks (~> 0.9.18) mdl (~> 0.5) nokogiri (>= 1.10.8) + puffing-billy (~> 2.4.0) puma (>= 4.3) rails-controller-testing (~> 1.0) rspec-cells (~> 0.3.4) @@ -160,7 +161,7 @@ PATH webmock (~> 3.6) wisper-rspec (~> 1.0) decidim-elections (0.24.0.dev) - decidim-bulletin_board (= 0.2.0) + decidim-bulletin_board (= 0.6.1) decidim-core (= 0.24.0.dev) decidim-forms (= 0.24.0.dev) decidim-proposals (= 0.24.0.dev) @@ -281,7 +282,7 @@ GEM public_suffix (>= 2.0.2, < 5.0) anchored (1.1.0) arel (9.0.0) - ast (2.4.1) + ast (2.4.2) autoprefixer-rails (8.6.5) execjs axiom-types (0.1.1) @@ -308,13 +309,13 @@ GEM browser (2.7.1) builder (3.2.4) byebug (11.1.3) - capybara (3.33.0) + capybara (3.35.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (2.1.0) activemodel (>= 5.0.0) @@ -335,7 +336,7 @@ GEM actionpack (>= 3.0) cells (>= 4.1.6, < 5.0.0) charlock_holmes (0.7.7) - chef-utils (16.6.14) + chef-utils (16.9.29) childprocess (3.0.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) @@ -348,7 +349,9 @@ GEM coffee-script-source (1.12.2) colorize (0.8.1) concurrent-ruby (1.1.7) - crack (0.4.4) + cookiejar (0.3.3) + crack (0.4.5) + rexml crass (1.0.6) css_parser (1.8.0) addressable @@ -358,12 +361,11 @@ GEM db-query-matchers (0.9.0) activesupport (>= 4.0, <= 6.0) rspec (~> 3.0) - decidim-bulletin_board (0.2.0) - activemodel (~> 5.0, >= 5.0.0.1) - activesupport (~> 5.0, >= 5.0.0.1) + decidim-bulletin_board (0.6.1) byebug (~> 11.0) graphlient (~> 0.4.0) - jwt + jwt (~> 2.2.2) + rails (>= 5.0.0) wisper (~> 2.0.0) declarative-builder (0.1.0) declarative-option (< 0.2.0) @@ -386,26 +388,38 @@ GEM doc2text (0.4.3) nokogiri (~> 1.11.1) rubyzip (~> 2.3.0) - docile (1.3.2) + docile (1.3.5) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) doorkeeper (5.4.0) railties (>= 5) doorkeeper-i18n (4.0.1) + em-http-request (1.1.7) + addressable (>= 2.3.4) + cookiejar (!= 0.3.1) + em-socksify (>= 0.3) + eventmachine (>= 1.0.3) + http_parser.rb (>= 0.6.0) + em-socksify (0.3.2) + eventmachine (>= 1.0.0.beta.4) + em-synchrony (1.0.6) + eventmachine (>= 1.0.0.beta.1) equalizer (0.0.11) - erb_lint (0.0.35) + erb_lint (0.0.37) activesupport better_html (~> 1.0.7) html_tokenizer parser (>= 2.7.1.4) rainbow - rubocop (~> 0.79) + rubocop smart_properties erbse (0.1.4) temple erubi (1.9.0) etherpad-lite (0.3.0) rest-client (>= 1.6) + eventmachine (1.2.7) + eventmachine_httpserver (0.2.1) excon (0.78.1) execjs (2.7.0) factory_bot (4.11.1) @@ -464,9 +478,10 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) + http_parser.rb (0.6.0) i18n (1.8.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.31) + i18n-tasks (0.9.33) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -593,7 +608,7 @@ GEM activerecord (>= 4.2) request_store (~> 1.1) parallel (1.20.1) - parser (2.7.2.0) + parser (3.0.0.0) ast (~> 2.4.1) pg (1.1.4) pg_search (2.3.5) @@ -607,6 +622,14 @@ GEM actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) public_suffix (4.0.5) + puffing-billy (2.4.1) + addressable (~> 2.5) + em-http-request (~> 1.1, >= 1.1.0) + em-synchrony + eventmachine (~> 1.2) + eventmachine_httpserver + http_parser.rb (~> 0.6.0) + multi_json puma (5.1.1) nio4r (~> 2.0) racc (1.5.2) @@ -666,7 +689,7 @@ GEM wisper (>= 1.6.1) redcarpet (3.5.1) redis (4.2.5) - regexp_parser (1.8.2) + regexp_parser (2.0.3) request_store (1.5.0) rack (>= 1.4) responders (3.0.1) @@ -718,17 +741,17 @@ GEM rubocop-ast (>= 0.5.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (1.1.1) + rubocop-ast (1.4.1) parser (>= 2.7.1.5) - rubocop-rails (2.8.1) + rubocop-rails (2.9.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.87.0) + rubocop (>= 0.90.0, < 2.0) rubocop-rspec (1.44.1) rubocop (~> 0.87) rubocop-ast (>= 0.7.1) ruby-ole (1.2.12.2) - ruby-progressbar (1.10.1) + ruby-progressbar (1.11.0) ruby-vips (2.0.17) ffi (~> 1.9) ruby2_keywords (0.0.2) @@ -785,7 +808,7 @@ GEM thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - tomlrb (1.3.0) + tomlrb (2.0.1) truncato (0.7.11) htmlentities (~> 4.3.1) nokogiri (>= 1.7.0, <= 2.0) @@ -816,7 +839,7 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webmock (3.10.0) + webmock (3.11.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/decidim-generators/lib/decidim/generators/app_templates/secrets.yml.erb b/decidim-generators/lib/decidim/generators/app_templates/secrets.yml.erb index eff452ad6fe90..092f4790ebea5 100644 --- a/decidim-generators/lib/decidim/generators/app_templates/secrets.yml.erb +++ b/decidim-generators/lib/decidim/generators/app_templates/secrets.yml.erb @@ -34,7 +34,8 @@ default: &default api_version: "1.2.1" bulletin_board: identification_private_key: {"kty":"RSA","n":"pNgMt8lnPDD3TlWYGhRiV1oZkPQmnLdiUzwyb_-35qKD9k-HU86xo0uSgoOUWkBtnvFscq8zNDPAGAlZVokaN_z9ksZblSce0LEl8lJa3ICgghg7e8vg_7Lz5dyHSQ3PCLgenyFGcL401aglDde1Xo4ujdz33Lklc4U9zoyoLUI2_viYmNOU6n5Mn0sJd30FeICMrLD2gX46pGe3MGug6groT9EvpKcdOoJHKoO5yGSVaeY5-Bo3gngvlgjlS2mfwjCtF4NYwIQSd2al-p4BKnuYAVKRSgr8rYnnjhWfJ4GsCaqiyXNi5NPYRV6gl_cx_1jUcA1rRJqQR32I8c8QbAXm5qNO4URcdaKys9tNcVgXBL1FsSdbrLVVFWen1tfWNfHm-8BjiWCWD79-uk5gI0SjC9tWvTzVvswWXI5weNqqVXqpDydr46AsHE2sG40HRCR3UF3LupT-HwXTcYcOZr5dJClJIsU3Hrvy4wLssub69YSNR1Jxn-KX2vUc06xY8CNIuSMpfufEq5cZopL6O2l1pRsW1FQnF3s078_Y9MaQ1gPyBo0IipLBVUj5IjEIfPuiEk4jxkiUYDeqzf7bAvSFckp94yLkRWTs_pEZs7b_ogwRG6WMHjtcaNYe4CufhIm9ekkKDeAWOPRTHfKNmohRBh09XuvSjqrx5Z7rqb8","e":"AQAB","kid":"b8dba1459df956d60107690c34fa490db681eac4f73ffaf6e4055728c02ddc8e","d":"Uh3KIBe1VJez6pLbBUrYPlmE2N-3CGSWF46qNX62lq6ofB_b8xTJCuaPonJ3iYoE0aPEeVDrefq5m3-0wFXl-LQPgXlMj_1_7UgB9jeuSZ_N1WDK6P2EJPx5YS09O1gkpVxK7Mx_sZQe77wmUUH-eI7tg__qfUrB7E0Yn_cTpBATI2qlYaQsz6-A7e1MVvixq_ilmzVAZvuBrPp5mCZVb6FlXrV_PU9-UPIrD3O1La1lfO6SPBSbSGQkmGHwD2QbkHn9D_R_Vs-z_0TkM_dX71jIPQhrle3pN222KuJ8eQqwr9QP6biQMBuT5eKgr3MVtfUDRpp4sCEq9GIFwSd8LvbmGPrOoz8ueOEQ05nisIBQuOTYiWpYs2CEV062HR1bLFRLDUcSlflGNr0bgiXTUFx4wxRG06OaI-rQ6nG3M8TE0I0phMNCG3c7YyV28z_k2I65oQF9aKtiwFwc0YsUSGPTOFZGWHuCCPLm0lFeebpI_JIYqIv70NJxbSZEBY8DAIqZPqP6y_CRo2_C7piCgsjg9pnF8cp45vz4L6DWZ0Tumc_5aRuqIBkYXXwP9TjqhzxL-2SQHIqUAjj6Y6S35tZT6ekZSbnPIKX_e42y6bDT_Ztf01QfKiTkcx3_I8RwOuh6CzJzr72AykQpU3XKOKF1x1GBtYyrno4jG5LgaGE","p":"1UARZ-rRnpKG5NHKlXTys3irCy-d91edHL3fEIzDKvhMRQCIWh7dt8l0_sIpcBF-EbVilbFKj7yfgZBTr8EkAXHgweayK8rnlMqi2jte1_u-5DBtrGVVUTSQltSLDOZHK5QfUxVK6Bbk8K5ROLvef91oNgnSNWNOeoCZdlS55nMZcAgY_6mxSuuMq54Tgy8o4Ip890-ZEYY6OSFXhU-ieoGO4Jw--c6QzmCa3gGo2oVClidMNaM1jquK4Pj6xaoxR2NWeIX9Ix7k1P2B24pegyHXjSIpQ6JYdn352VViXi2tx7TTJh6ClNVjgoRmL4Gfy_IJNx0GhF5OB3yughUc7w","q":"xePJGBt466qM9F0BPxWFjyWbIs_GNXr-lBGASui0Z94cfgFbsZwqRsWQEf7jDVQsDNVnPSWZ_Wd6UqoQaIxc0tE8gaokPG6A4EUDyoLaZ231ZydDVoWof8FnPDaJwrcPwZ4R6ZLKGmkfytCZuU9I_9B4uuV0dyjEzKfS-Os3UcLumKPlgJ71OZAb49GTqUHuTePcSJjyYOYXx6eE7i_1m8TjU9Ut18BJNQhLqWmerA6X1ijbR2_syY6GXhGSfciSBH8xVkiUnqXb2jt1bE8nwWw-Sam5ikjzNbXqqs978IcCE5HTddQmy99bwuArA8PLqIFj3OOO1CSo8oyn2XDgMQ","dp":"Diky_rOZN-6DBq7nxQT_GOvqb9O5qbMnu8DgDzlJvJDAf9SJOXLTRmEaY9CA7_A5bvOcmFQtn13nObNb20_4FCB7zGSFcGMI_dh2-Ab5RV5yTrTok4onID1dXKbAlRq1ny825U2Eq-TZTyJEQoA3RkZtpSkBzInLrFbd2f3GWodKKSZggpnCLDd4H-1fXlbDYCXSJpoikAdZ1nFgXnnrUDdKRaAajnwpIYtIvXVewSQYR-BULzunUtIRZt8hx_6FRzhRha9gH_TtPTeYZ_vISuz0Y2rhUpx1Q2kaLlR9M8PUxm47l0xvX3LMKN6h6oWxFtn7wq0qwZ-Bjv24mOrOAQ","dq":"nXGD10hURrwk9W7hxP0sjB2Rdnr06iv3THs4JWFL16_h32bZO1BSWoho_chbgYlMmtFXGFFIWVLxAcAI2gWC_MA4cbmapvIMW2LNh1vgxJW5v95_NuGUlECeEEwcAu1-_b7z5XBCmAy3nLem9sbb_5wv0hMpPH0VRvbnZeBO3SBIkO0lddYCqU-8wN9HqkyoexQleSUnAm1O0iy4GIHT2aEmdNaRaKy2EhmNiTZdZeseZueOvyGPtTVONp2ofacMdcN0z39jr22qo9DWtdusd7nVPOpqkllEF6GrGUeHBnGD92n4YjDuxRnqefu8fXxUFrcLav0p8CNSv9ek291woQ","qi":"w6hfKEBLLHRWPkjajgxZyyetj-UFfVkILRT0plOllJ2JV8whcOXRXbiXH2r8zqMeyMFrrMwmuvv4TVQaruKB0ZQOG7Tz5Lw0RZEREOLnBwc3vSi_iLd-jBz01LdExTpqsAHMkaMQR9x62J8DE1ZNxVdn3ELYKik0f1L2r_WErzhvT1uq69HAybUp6WHcFYH0PSqHg4LOneXAdU1_g-ji2Zn9dlA_2oYGQ5S6JXPV7v2IVbEFpxyVD1lPbFT0iKhyZZevictjgD_JGHveIVqsq5w0Csyz08h0oEW9hYEq-4bquMxSf18gjldoS5uQPD7FUECgL8bxsCdc4hP6UEKYGw"} - server: http://localhost:8000/api + server: http://bulletin-board.lvh.me:8000/api + websocket_url: ws://bulletin-board.lvh.me:8000/cable api_key: "89Ht70GZNcicu8WEyagz_rRae6brbqZAGuBEICYBCii-PTV3MAstAtx1aRVe5H5YfODi-JgYPvyf9ZMH7tOeZ15e3mf9B2Ymgw7eknvBFMRP213YFGo1SPn_C4uLK90G" number_of_trustees: 2 authority_name: "Decidim Test Authority" @@ -83,6 +84,7 @@ production: bulletin_board: identification_private_key: <%%= ENV["BULLETIN_BOARD_IDENTIFICATION_PRIVATE_KEY"] %> server: <%%= ENV["BULLETIN_BOARD_SERVER"] %> + websocket_url: <%%= ENV["BULLETIN_BOARD_WEBSOCKET_URL"] %> api_key: <%%= ENV["BULLETIN_BOARD_API_KEY"] %> number_of_trustees: <%%= ENV["BULLETIN_BOARD_NUMBER_OF_TRUSTEES"] %> authority_name: <%%= ENV["BULLETIN_BOARD_AUTHORITY_NAME"] %> diff --git a/decidim-initiatives/app/queries/decidim/initiatives/initiatives_promoted.rb b/decidim-initiatives/app/queries/decidim/initiatives/initiatives_promoted.rb index 12b8fbc1aaee7..3883ba8a3843a 100644 --- a/decidim-initiatives/app/queries/decidim/initiatives/initiatives_promoted.rb +++ b/decidim-initiatives/app/queries/decidim/initiatives/initiatives_promoted.rb @@ -25,7 +25,7 @@ def query Initiative .joins(:committee_members) .where("decidim_initiatives_committee_members.state = 2") - .where("decidim_initiatives_committee_members.decidim_users_id = ?", user.id) + .where(decidim_initiatives_committee_members: { decidim_users_id: user.id }) end end end diff --git a/decidim-initiatives/lib/decidim/initiatives/query_extensions.rb b/decidim-initiatives/lib/decidim/initiatives/query_extensions.rb index bb2fc54920ac9..87d324310ff97 100644 --- a/decidim-initiatives/lib/decidim/initiatives/query_extensions.rb +++ b/decidim-initiatives/lib/decidim/initiatives/query_extensions.rb @@ -50,13 +50,13 @@ def initiatives_type(id:) def initiatives(filter: {}, order: {}) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :initiatives }.first - Decidim::Core::ParticipatorySpaceList.new(manifest: manifest).call(object, { filter: filter, order: order }, context) + Decidim::Core::ParticipatorySpaceListBase.new(manifest: manifest).call(object, { filter: filter, order: order }, context) end def initiative(id: nil) manifest = Decidim.participatory_space_manifests.select { |m| m.name == :initiatives }.first - Decidim::Core::ParticipatorySpaceFinder.new(manifest: manifest).call(object, { id: id }, context) + Decidim::Core::ParticipatorySpaceFinderBase.new(manifest: manifest).call(object, { id: id }, context) end end end diff --git a/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_finder.rb b/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_finder.rb deleted file mode 100644 index e214539f01664..0000000000000 --- a/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_finder.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module ParticipatoryProcesses - # Adds slug finder for participatory_processes - class ParticipatoryProcessFinder < Decidim::Core::ParticipatorySpaceFinder - argument :slug, String, "The slug of the participatory process" - end - end -end diff --git a/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_list.rb b/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_list.rb deleted file mode 100644 index 111206059aa0c..0000000000000 --- a/decidim-participatory_processes/app/functions/decidim/participatory_processes/participatory_process_list.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module ParticipatoryProcesses - # Adds some filter values for participatory_processes - class ParticipatoryProcessList < Decidim::Core::ParticipatorySpaceListBase - argument :filter, ParticipatoryProcessInputFilter, "This argument let's you filter the results" - argument :order, ParticipatoryProcessInputSort, "This argument let's you order the results" - end - end -end diff --git a/decidim-sortitions/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb b/decidim-sortitions/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb index 16e1e43901e74..0b4157981523c 100644 --- a/decidim-sortitions/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb +++ b/decidim-sortitions/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb @@ -42,7 +42,7 @@ def query .not_hidden .where(component: sortition.decidim_proposals_component) .where("decidim_proposals_proposals.created_at < ?", request_timestamp) - .where("decidim_categorizations.decidim_category_id = ?", category.id) + .where(decidim_categorizations: { decidim_category_id: category.id }) .order(id: :asc) end diff --git a/decidim_app-design/Gemfile.lock b/decidim_app-design/Gemfile.lock index 73c433ff16bd8..e267b99ba1d43 100644 --- a/decidim_app-design/Gemfile.lock +++ b/decidim_app-design/Gemfile.lock @@ -142,6 +142,7 @@ PATH i18n-tasks (~> 0.9.18) mdl (~> 0.5) nokogiri (>= 1.10.8) + puffing-billy (~> 2.4.0) puma (>= 4.3) rails-controller-testing (~> 1.0) rspec-cells (~> 0.3.4) @@ -160,7 +161,7 @@ PATH webmock (~> 3.6) wisper-rspec (~> 1.0) decidim-elections (0.24.0.dev) - decidim-bulletin_board (= 0.2.0) + decidim-bulletin_board (= 0.6.1) decidim-core (= 0.24.0.dev) decidim-forms (= 0.24.0.dev) decidim-proposals (= 0.24.0.dev) @@ -308,13 +309,13 @@ GEM browser (2.7.1) builder (3.2.4) byebug (11.1.3) - capybara (3.33.0) + capybara (3.35.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (2.1.0) activemodel (>= 5.0.0) @@ -335,7 +336,7 @@ GEM actionpack (>= 3.0) cells (>= 4.1.6, < 5.0.0) charlock_holmes (0.7.7) - chef-utils (16.6.14) + chef-utils (16.9.29) childprocess (3.0.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) @@ -348,7 +349,9 @@ GEM coffee-script-source (1.12.2) colorize (0.8.1) concurrent-ruby (1.1.7) - crack (0.4.4) + cookiejar (0.3.3) + crack (0.4.5) + rexml crass (1.0.6) css_parser (1.8.0) addressable @@ -358,12 +361,11 @@ GEM db-query-matchers (0.9.0) activesupport (>= 4.0, <= 6.0) rspec (~> 3.0) - decidim-bulletin_board (0.2.0) - activemodel (~> 5.0, >= 5.0.0.1) - activesupport (~> 5.0, >= 5.0.0.1) + decidim-bulletin_board (0.6.1) byebug (~> 11.0) graphlient (~> 0.4.0) - jwt + jwt (~> 2.2.2) + rails (>= 5.0.0) wisper (~> 2.0.0) declarative-builder (0.1.0) declarative-option (< 0.2.0) @@ -392,20 +394,32 @@ GEM doorkeeper (5.4.0) railties (>= 5) doorkeeper-i18n (4.0.1) + em-http-request (1.1.7) + addressable (>= 2.3.4) + cookiejar (!= 0.3.1) + em-socksify (>= 0.3) + eventmachine (>= 1.0.3) + http_parser.rb (>= 0.6.0) + em-socksify (0.3.2) + eventmachine (>= 1.0.0.beta.4) + em-synchrony (1.0.6) + eventmachine (>= 1.0.0.beta.1) equalizer (0.0.11) - erb_lint (0.0.35) + erb_lint (0.0.37) activesupport better_html (~> 1.0.7) html_tokenizer parser (>= 2.7.1.4) rainbow - rubocop (~> 0.79) + rubocop smart_properties erbse (0.1.4) temple erubi (1.9.0) etherpad-lite (0.3.0) rest-client (>= 1.6) + eventmachine (1.2.7) + eventmachine_httpserver (0.2.1) excon (0.78.1) execjs (2.7.0) factory_bot (4.11.1) @@ -463,9 +477,10 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) + http_parser.rb (0.6.0) i18n (1.8.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.31) + i18n-tasks (0.9.33) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -543,7 +558,7 @@ GEM mixlib-cli (2.1.8) mixlib-config (3.0.9) tomlrb - mixlib-shellout (3.1.7) + mixlib-shellout (3.2.2) chef-utils msgpack (1.3.3) multi_json (1.15.0) @@ -605,6 +620,14 @@ GEM actionmailer (>= 3) premailer (~> 1.7, >= 1.7.9) public_suffix (4.0.6) + puffing-billy (2.4.1) + addressable (~> 2.5) + em-http-request (~> 1.1, >= 1.1.0) + em-synchrony + eventmachine (~> 1.2) + eventmachine_httpserver + http_parser.rb (~> 0.6.0) + multi_json puma (5.0.2) nio4r (~> 2.0) rack (2.2.3) @@ -721,10 +744,10 @@ GEM rubocop-faker (1.1.0) faker (>= 2.12.0) rubocop (>= 0.82.0) - rubocop-rails (2.8.1) + rubocop-rails (2.9.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.87.0) + rubocop (>= 0.90.0, < 2.0) rubocop-rspec (1.43.2) rubocop (~> 0.87) ruby-ole (1.2.12.2) @@ -785,7 +808,7 @@ GEM thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - tomlrb (1.3.0) + tomlrb (2.0.1) truncato (0.7.11) htmlentities (~> 4.3.1) nokogiri (>= 1.7.0, <= 2.0) @@ -816,7 +839,7 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webmock (3.9.3) + webmock (3.11.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/docs/modules/services/pages/elections_bulletin_board.adoc b/docs/modules/services/pages/elections_bulletin_board.adoc index 759695713a79d..4acfc8c151313 100644 --- a/docs/modules/services/pages/elections_bulletin_board.adoc +++ b/docs/modules/services/pages/elections_bulletin_board.adoc @@ -44,3 +44,7 @@ Maybe this is already done, as it was included in the Decidim applications gener ---- After restarting the Decidim instance, administrator users will be able to create elections on the configured Bulletin Board. + +== Scheduled tasks + +A *crontab* line must be added to your server to be able to open and close the Ballot Box for the elections automatically. You could use https://github.com/javan/whenever[Whenever] to manage it directly from the APP. You probably want to schedule a `bundle exec rake decidim_elections:scheduled_tasks` every hour.