" }
let(:deprecated_url) { "https://#{deprecated_endpoint}/xxxx?response-content-disposition=inline%3Bfilename%3D\"BuPa23_reglement-interieur.pdf\"%3Bfilename*%3DUTF-8''BuPa23_r%25C3%25A8glement-int%25C3%25A9rieur.pdf&response-content-type=application%2Fpdf" }
- let!(:blob) { ActiveStorage::Blob.create_after_upload!(filename: "BuPa23_reglement-interieur.pdf", io: File.open("spec/fixtures/BuPa23_reglement-interieur.pdf"), content_type: "application/pdf") }
+ let!(:blob) { ActiveStorage::Blob.create_and_upload!(filename: "BuPa23_reglement-interieur.pdf", io: File.open("spec/fixtures/BuPa23_reglement-interieur.pdf"), content_type: "application/pdf") }
let(:blob_path) { Rails.application.routes.url_helpers.rails_blob_path(ActiveStorage::Blob.find(blob.id), only_path: true) }
describe "#repair" do
diff --git a/spec/lib/decidim/translator_configuration_helper_spec.rb b/spec/lib/decidim/translator_configuration_helper_spec.rb
new file mode 100644
index 0000000000..7d9e90a1a2
--- /dev/null
+++ b/spec/lib/decidim/translator_configuration_helper_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "decidim/translator_configuration_helper"
+
+RSpec.describe Decidim::TranslatorConfigurationHelper do
+ let!(:original_queue_adapter) { Rails.configuration.active_job.queue_adapter }
+ let!(:original_enable_machine_translations) { Decidim.enable_machine_translations }
+ let(:with_incompatible_backend) { Rails.configuration.active_job.queue_adapter = :async }
+ let(:with_compatible_backend) { Rails.configuration.active_job.queue_adapter = :something }
+ let(:with_translations_enabled) { Decidim.enable_machine_translations = true }
+ let(:with_translations_disabled) { Decidim.enable_machine_translations = false }
+
+ after do
+ Rails.configuration.active_job.queue_adapter = original_queue_adapter
+ Decidim.enable_machine_translations = original_enable_machine_translations
+ end
+
+ describe ".able_to_seed?" do
+ context "when Decidim translations are enabled" do
+ before { with_translations_enabled }
+
+ context "when the backend is 'async'" do
+ before { with_incompatible_backend }
+
+ it "raises an error" do
+ expect do
+ Decidim::TranslatorConfigurationHelper.able_to_seed?
+ end.to raise_error RuntimeError, /^You can't seed the database/
+ end
+ end
+
+ context "when the backend is not 'async'" do
+ before { with_compatible_backend }
+
+ it "returns nil" do
+ expect(Decidim::TranslatorConfigurationHelper.able_to_seed?).to be_nil
+ end
+ end
+ end
+
+ context "when Decidim translations are disabled" do
+ before { with_translations_disabled }
+
+ it "returns true" do
+ expect(Decidim::TranslatorConfigurationHelper.able_to_seed?).to be true
+ end
+ end
+ end
+
+ describe ".compatible_backend" do
+ context "with an 'async' backend" do
+ before { with_incompatible_backend }
+
+ it "returns false" do
+ expect(Decidim::TranslatorConfigurationHelper.compatible_backend?).to be false
+ end
+ end
+
+ context "with another backend" do
+ before { with_compatible_backend }
+
+ it "returns true" do
+ expect(Decidim::TranslatorConfigurationHelper.compatible_backend?).to be true
+ end
+ end
+ end
+
+ describe ".translator_activated?" do
+ context "when translations are active" do
+ before { with_translations_enabled }
+
+ it "returns true" do
+ expect(Decidim::TranslatorConfigurationHelper.translator_activated?).to be true
+ end
+ end
+
+ context "when translations are inactive" do
+ before { with_translations_disabled }
+
+ it "returns true" do
+ expect(Decidim::TranslatorConfigurationHelper.translator_activated?).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/decidim_app/decidim_initiatives_spec.rb b/spec/lib/decidim_app/decidim_initiatives_spec.rb
index 810e44bbbd..4aa3499a90 100644
--- a/spec/lib/decidim_app/decidim_initiatives_spec.rb
+++ b/spec/lib/decidim_app/decidim_initiatives_spec.rb
@@ -5,6 +5,17 @@
describe DecidimApp::DecidimInitiatives do
subject { described_class }
+ describe ".apply_configuration" do
+ it "sets the configuration values" do
+ skip_if_undefined "Decidim::Initiatives", "decidim-initiatives"
+
+ allow(Decidim::Initiatives).to receive(:configure)
+ subject.apply_configuration
+
+ expect(Decidim::Initiatives).to have_received(:configure)
+ end
+ end
+
describe "#creation_enabled?" do
it "returns true" do
expect(subject).to be_creation_enabled
@@ -109,6 +120,21 @@
end
end
+ describe ".default_components" do
+ it "handles empty array string" do
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :default_components).and_return(["[]"])
+
+ expect(subject.default_components).to eq []
+ end
+
+ it "returns the configured value" do
+ expected = ["a", 1, true]
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :default_components).and_return(expected)
+
+ expect(subject.default_components).to eq expected
+ end
+ end
+
describe "#first_notification_percentage" do
context "when rails secret '25'" do
before do
@@ -196,4 +222,48 @@
end
end
end
+
+ describe ".print_enabled?" do
+ context "when rails secret has a value" do
+ [10, true, "hello"].each do |value|
+ it "returns false for '#{value}'" do
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :print_enabled).and_return(value)
+
+ expect(subject.print_enabled?).to be true
+ end
+ end
+ end
+
+ context "when rails secret has no value" do
+ [false, nil, ""].each do |value|
+ it "returns false for '#{value}'" do
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :print_enabled).and_return(value)
+
+ expect(subject.print_enabled?).to be false
+ end
+ end
+ end
+ end
+
+ describe ".do_not_require_authorization?" do
+ context "when rails secret has a value" do
+ [10, true, "hello"].each do |value|
+ it "returns false for '#{value}'" do
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :do_not_require_authorization).and_return(value)
+
+ expect(subject.do_not_require_authorization?).to be true
+ end
+ end
+ end
+
+ context "when rails secret has no value" do
+ [false, nil, ""].each do |value|
+ it "returns false for '#{value}'" do
+ allow(Rails.application.secrets).to receive(:dig).with(:decidim, :initiatives, :do_not_require_authorization).and_return(value)
+
+ expect(subject.do_not_require_authorization?).to be false
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/decidim_app/sentry_setup_spec.rb b/spec/lib/decidim_app/sentry_setup_spec.rb
index 93a23c04d7..ef1a209afd 100644
--- a/spec/lib/decidim_app/sentry_setup_spec.rb
+++ b/spec/lib/decidim_app/sentry_setup_spec.rb
@@ -52,6 +52,23 @@
end
end
+ describe "#sample_trace" do
+ let(:transaction_name) { "/some_page" }
+ let(:context) { { transaction_context: { op: "http", name: transaction_name } } }
+
+ context "when transaction is about the health check" do
+ let(:transaction_name) { "/health_check" }
+
+ it "returns 0" do
+ expect(subject.send(:sample_trace, context)).to eq 0.0
+ end
+ end
+
+ it "returns a Float" do
+ expect(subject.send(:sample_trace, context)).to be_a Float
+ end
+ end
+
describe ".ip" do
it "returns the ip" do
expect(subject.send(:ip)).to eq("123.123.123.123")
diff --git a/spec/lib/migrations_fixer_spec.rb b/spec/lib/migrations_fixer_spec.rb
new file mode 100644
index 0000000000..f926860d13
--- /dev/null
+++ b/spec/lib/migrations_fixer_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require "migrations_fixer"
+
+RSpec.describe MigrationsFixer do
+ let(:logger) { Logger.new nil }
+ let(:migration_path_env) { Rails.root.to_s }
+ let(:instance) { described_class.new logger }
+
+ before do
+ @old_migration_path_env = ENV.fetch("MIGRATIONS_PATH", nil)
+ ENV["MIGRATIONS_PATH"] = migration_path_env
+ end
+
+ after do
+ ENV["MIGRATIONS_PATH"] = @old_migration_path_env # rubocop:disable RSpec/InstanceVariable
+ end
+
+ describe ".new" do
+ context "with valid parameters" do
+ it "sets the logger" do
+ expect(instance.logger).to eq logger
+ end
+
+ it "sets the migrations path" do
+ expect(instance.migrations_path).not_to be_blank
+ end
+ end
+
+ context "with missing logger" do
+ let(:logger) { nil }
+
+ it "raises an exception" do
+ expect do
+ described_class.new logger
+ end.to raise_error "Undefined logger"
+ end
+ end
+
+ context "with missing environment" do
+ let(:migration_path_env) { nil }
+
+ it "raises an exception" do
+ expect do
+ described_class.new logger
+ end.to raise_error "Invalid configuration, aborting"
+ end
+ end
+
+ context "with non-existing MIGRATIONS_PATH variable" do
+ let(:migration_path_env) { "/some/inexistant/dir" }
+
+ it "raises an exception" do
+ expect do
+ described_class.new logger
+ end.to raise_error "Invalid configuration, aborting"
+ end
+ end
+
+ context "with missing project migrations" do
+ it "raises an exception" do
+ allow(ActiveRecord::Base.connection.migration_context.migrations_paths).to receive(:first).and_return "/some/invalid/directory"
+
+ expect do
+ described_class.new logger
+ end.to raise_error "Invalid configuration, aborting"
+ end
+ end
+ end
+end
diff --git a/spec/lib/rails_migrations_spec.rb b/spec/lib/rails_migrations_spec.rb
new file mode 100644
index 0000000000..f47d15c488
--- /dev/null
+++ b/spec/lib/rails_migrations_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require "rails_migrations"
+require "migrations_fixer"
+
+class FakeMigrationsFixer
+ attr_reader :logger
+
+ def initialize(logger)
+ @logger = logger
+ end
+
+ def osp_app_path
+ "/some/dir"
+ end
+
+ def migrations_path
+ "/something_else"
+ end
+end
+
+RSpec.describe RailsMigrations do
+ let(:logger) { Logger.new nil }
+ let(:migration_fixer) { FakeMigrationsFixer.new logger }
+ let(:instance) { described_class.new migration_fixer }
+ let(:migrations_status) do
+ [
+ ["up", "20230824135802", "Change something in db structure"],
+ ["down", "20230824135803", "Change something else"],
+ ["down", "20230824135804", "********** NO FILE **********"]
+ ]
+ end
+
+ before do
+ allow(instance).to receive(:migration_status).and_return migrations_status
+ instance.reload_migrations!
+ end
+
+ describe "#reload_down!" do
+ it "reloads and find down migrations" do
+ allow(instance).to receive(:reload_migrations!)
+ allow(instance).to receive(:down)
+
+ instance.reload_down!
+
+ aggregate_failures do
+ expect(instance).to have_received(:reload_migrations!)
+ expect(instance).to have_received(:down)
+ end
+ end
+ end
+
+ describe "#down" do
+ it "returns all migrations marked 'down'" do
+ expect(instance.down.size).to eq 2
+ end
+ end
+
+ describe "#reload_migrations!" do
+ it "resets @fetch_all" do
+ new_list = [1, 2, 3]
+ allow(instance).to receive(:migration_status).and_return new_list
+
+ instance.reload_migrations!
+ expect(instance.fetch_all).to eq new_list
+ end
+ end
+
+ describe "#display_status!" do
+ it "logs statuses" do
+ allow(logger).to receive(:info)
+
+ instance.display_status!
+
+ expect(logger).to have_received(:info).exactly(3).times
+ end
+ end
+
+ describe "#not_found" do
+ it "returns the amount of missing migrations files" do
+ expect(instance.not_found.size).to eq 1
+ end
+ end
+
+ describe "#versions_down_but_already_passed" do
+ it "returns the list of possible files for missing versions" do
+ allow(Dir).to receive(:glob).and_return ["20230824135804_change_something_else_again.rb"]
+ expect(instance.versions_down_but_already_passed).to eq ["20230824135804"]
+ end
+ end
+end
diff --git a/spec/lib/rspec_runner_spec.rb b/spec/lib/rspec_runner_spec.rb
index 70db486aa5..5411e53473 100644
--- a/spec/lib/rspec_runner_spec.rb
+++ b/spec/lib/rspec_runner_spec.rb
@@ -69,6 +69,38 @@ module Decidim
end
end
+ describe "#for" do
+ context "with missing arguments" do
+ it "fails without pattern" do
+ expect do
+ described_class.for nil, mask, slice
+ end.to raise_error("Missing pattern")
+ end
+
+ it "fails without mask" do
+ expect do
+ described_class.for pattern, nil, slice
+ end.to raise_error("Missing mask")
+ end
+
+ it "fails without slice" do
+ expect do
+ described_class.for pattern, mask, nil
+ end.to raise_error("Missing slice")
+ end
+ end
+
+ context "with all the arguments" do
+ # This is tightly coupled with the implementation
+ it "runs the suite" do
+ allow(described_class).to receive(:new).and_return subject
+ allow(subject).to receive(:run).and_return "__success__"
+
+ expect(described_class.for(pattern, mask, slice)).to eq "__success__"
+ end
+ end
+ end
+
describe "#sliced_files" do
before do
allow(Dir).to receive(:glob).and_return(files)
diff --git a/spec/lib/tasks/decidim/db/admin_log/clear_spec.rb b/spec/lib/tasks/decidim/db/admin_log/clear_spec.rb
new file mode 100644
index 0000000000..2f03d7b1a0
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/admin_log/clear_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:admin_log:clean", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'clear' method" do
+ stub = Decidim::ActionLogService.new
+ allow(Decidim::ActionLogService).to receive(:new).and_return stub
+ allow(stub).to receive(:clear).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:clear)
+ end
+end
diff --git a/spec/lib/tasks/decidim/db/admin_log/orphans_spec.rb b/spec/lib/tasks/decidim/db/admin_log/orphans_spec.rb
new file mode 100644
index 0000000000..1acb661c93
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/admin_log/orphans_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:admin_log:orphans", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'orphans' method" do
+ stub = Decidim::ActionLogService.new
+ allow(Decidim::ActionLogService).to receive(:new).and_return stub
+ allow(stub).to receive(:orphans).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:orphans)
+ end
+end
diff --git a/spec/lib/tasks/decidim/db/notification/clear_spec.rb b/spec/lib/tasks/decidim/db/notification/clear_spec.rb
new file mode 100644
index 0000000000..9b5b9d29a0
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/notification/clear_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:notification:clean", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'clear' method" do
+ stub = Decidim::NotificationService.new
+ allow(Decidim::NotificationService).to receive(:new).and_return stub
+ allow(stub).to receive(:clear).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:clear)
+ end
+end
diff --git a/spec/lib/tasks/decidim/db/notification/orphans_spec.rb b/spec/lib/tasks/decidim/db/notification/orphans_spec.rb
new file mode 100644
index 0000000000..621a1ab025
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/notification/orphans_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:notification:orphans", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'orphans' method" do
+ stub = Decidim::NotificationService.new
+ allow(Decidim::NotificationService).to receive(:new).and_return stub
+ allow(stub).to receive(:orphans).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:orphans)
+ end
+end
diff --git a/spec/lib/tasks/decidim/db/surveys/clear_spec.rb b/spec/lib/tasks/decidim/db/surveys/clear_spec.rb
new file mode 100644
index 0000000000..a61433cbe8
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/surveys/clear_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:surveys:clean", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'clear' method" do
+ stub = Decidim::SurveysService.new
+ allow(Decidim::SurveysService).to receive(:new).and_return stub
+ allow(stub).to receive(:clear).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:clear)
+ end
+end
diff --git a/spec/lib/tasks/decidim/db/surveys/orphans_spec.rb b/spec/lib/tasks/decidim/db/surveys/orphans_spec.rb
new file mode 100644
index 0000000000..1d9747995a
--- /dev/null
+++ b/spec/lib/tasks/decidim/db/surveys/orphans_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim:db:surveys:orphans", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "invokes the 'orphans' method" do
+ stub = Decidim::SurveysService.new
+ allow(Decidim::SurveysService).to receive(:new).and_return stub
+ allow(stub).to receive(:orphans).and_return(true)
+
+ task.execute
+
+ expect(stub).to have_received(:orphans)
+ end
+end
diff --git a/spec/lib/tasks/decidim/repair/comments_spec.rb b/spec/lib/tasks/decidim/repair/comments_spec.rb
new file mode 100644
index 0000000000..8b1b7ca10b
--- /dev/null
+++ b/spec/lib/tasks/decidim/repair/comments_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "rake decidim:repair:comments", type: :task do
+ it "uses the appropriate service" do
+ allow(Decidim::RepairCommentsService).to receive(:run)
+
+ task.execute
+
+ expect(Decidim::RepairCommentsService).to have_received(:run).once
+ end
+
+ describe "logging" do
+ let!(:logger) { Logger.new($stdout) }
+
+ before do
+ # Stub the logger
+ allow(logger).to receive(:info)
+ allow(Logger).to receive(:new).and_return(logger)
+
+ allow(Decidim::RepairCommentsService).to receive(:run).and_return updated_comments_ids
+ end
+
+ context "when no nickname was repaired" do
+ let(:updated_comments_ids) { [] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("No comments updated")
+ end
+ end
+
+ context "when some nicknames were repaired" do
+ let(:updated_comments_ids) { [1, 2, 3] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("Updated comments ID : 1, 2, 3")
+ end
+ end
+ end
+end
diff --git a/spec/lib/tasks/decidim/repair/nickname_spec.rb b/spec/lib/tasks/decidim/repair/nickname_spec.rb
new file mode 100644
index 0000000000..60f157c18d
--- /dev/null
+++ b/spec/lib/tasks/decidim/repair/nickname_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "rake decidim:repair:nickname", type: :task do
+ it "uses the appropriate service" do
+ allow(Decidim::RepairNicknameService).to receive(:run)
+
+ task.execute
+
+ expect(Decidim::RepairNicknameService).to have_received(:run).once
+ end
+
+ describe "logging" do
+ let!(:logger) { Logger.new($stdout) }
+
+ before do
+ # Stub the logger
+ allow(logger).to receive(:info)
+ allow(Logger).to receive(:new).and_return(logger)
+
+ allow(Decidim::RepairNicknameService).to receive(:run).and_return updated_user_ids
+ end
+
+ context "when no nickname was repaired" do
+ let(:updated_user_ids) { [] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("No users updated")
+ end
+ end
+
+ context "when some nicknames were repaired" do
+ let(:updated_user_ids) { [1, 2, 3] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("Updated users ID : 1, 2, 3")
+ end
+ end
+ end
+end
diff --git a/spec/lib/tasks/repair_data_translations_spec.rb b/spec/lib/tasks/decidim/repair/translations_spec.rb
similarity index 55%
rename from spec/lib/tasks/repair_data_translations_spec.rb
rename to spec/lib/tasks/decidim/repair/translations_spec.rb
index 328994515b..f419c48485 100644
--- a/spec/lib/tasks/repair_data_translations_spec.rb
+++ b/spec/lib/tasks/decidim/repair/translations_spec.rb
@@ -36,4 +36,36 @@
task.execute
end
end
+
+ describe "logging" do
+ let!(:logger) { Logger.new($stdout) }
+
+ before do
+ # Stub the logger
+ allow(logger).to receive(:info)
+ allow(Logger).to receive(:new).and_return(logger)
+
+ allow(Decidim::RepairTranslationsService).to receive(:run).and_return updated_resources_ids
+ end
+
+ context "when no nickname was repaired" do
+ let(:updated_resources_ids) { [] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("No resources updated")
+ end
+ end
+
+ context "when some nicknames were repaired" do
+ let(:updated_resources_ids) { [1, 2, 3] }
+
+ it "logs a message" do
+ task.execute
+
+ expect(logger).to have_received(:info).with("Enqueued resources : 1, 2, 3")
+ end
+ end
+ end
end
diff --git a/spec/lib/tasks/repair_data_url_in_content_spec.rb b/spec/lib/tasks/decidim/repair/url_in_content_spec.rb
similarity index 100%
rename from spec/lib/tasks/repair_data_url_in_content_spec.rb
rename to spec/lib/tasks/decidim/repair/url_in_content_spec.rb
diff --git a/spec/lib/tasks/decidim_app/k8s/install_task_spec.rb b/spec/lib/tasks/decidim_app/k8s/install_task_spec.rb
new file mode 100644
index 0000000000..9386dde5a1
--- /dev/null
+++ b/spec/lib/tasks/decidim_app/k8s/install_task_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "rake decidim_app:k8s:install", type: :task do
+ it "preloads the Rails environment" do
+ expect(task.prerequisites).to include "environment"
+ end
+
+ it "calls db:migrate" do
+ expect(Rake::Task["db:migrate"]).to receive(:invoke)
+
+ task.execute
+ end
+end
diff --git a/spec/services/decidim/action_log_service_spec.rb b/spec/services/decidim/action_log_service_spec.rb
index 1f0a7de1e6..98d6fd600f 100644
--- a/spec/services/decidim/action_log_service_spec.rb
+++ b/spec/services/decidim/action_log_service_spec.rb
@@ -40,4 +40,16 @@
end.to change(Decidim::ActionLog, :count).from(10).to(0)
end
end
+
+ describe "#orphans_for" do
+ context "when the class does not exist" do
+ it "logs the error" do
+ logger = Logger.new($stdout)
+ allow(logger).to receive(:warn)
+ described_class.new(logger: logger).send(:orphans_for, "NonExistingClass")
+
+ expect(logger).to have_received(:warn).with("Skipping class : NonExistingClass")
+ end
+ end
+ end
end
diff --git a/spec/services/decidim/database_service_spec.rb b/spec/services/decidim/database_service_spec.rb
index 613f43064e..2e2d13d7ac 100644
--- a/spec/services/decidim/database_service_spec.rb
+++ b/spec/services/decidim/database_service_spec.rb
@@ -2,6 +2,18 @@
require "spec_helper"
+class FakeDatabaseService < Decidim::DatabaseService
+ def initialize(resource_types: nil, **args)
+ @resource_types = resource_types
+
+ super(**args)
+ end
+
+ def resource_types # rubocop:disable Style/TrivialAccessors
+ @resource_types
+ end
+end
+
describe Decidim::DatabaseService do
subject { described_class.new }
@@ -28,4 +40,19 @@
end.to raise_error RuntimeError, "Method clear_data_for isn't defined for Decidim::DatabaseService"
end
end
+
+ describe "when used as class parent" do
+ let(:fake_instance) { FakeDatabaseService.new(**instance_args) }
+ let(:instance_args) { {} }
+
+ describe "#orphans" do
+ context "with no resource type" do
+ let(:instance_args) { { resource_types: nil } }
+
+ it "returns nil" do
+ expect(fake_instance.orphans).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/decidim/s3_retention_service_spec.rb b/spec/services/decidim/s3_retention_service_spec.rb
new file mode 100644
index 0000000000..c6f46a9c3f
--- /dev/null
+++ b/spec/services/decidim/s3_retention_service_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Decidim::S3RetentionService do
+ let(:options) { {} }
+ let(:instance) { described_class.new(options) }
+
+ describe ".run" do
+ it "executes the service" do
+ allow(described_class).to receive(:new).with(options).and_return instance
+ allow(instance).to receive(:execute).and_return "__success__"
+
+ expect(described_class.run({})).to eq "__success__"
+ end
+ end
+
+ describe "#default_options" do
+ let(:option_keys) { instance.default_options.keys }
+
+ it "returns a hash" do
+ expect(instance.default_options).to be_a Hash
+ end
+
+ it "has keys existing in backup configuration" do
+ config_keys = Rails.application.config.backup[:s3sync].keys
+ .map { |k| "s3_#{k}".to_sym }
+
+ aggregate_failures do
+ option_keys.each do |key|
+ expect(config_keys).to include key
+ end
+ end
+ end
+ end
+
+ describe "#subfolder" do
+ it "returns a memoized string" do
+ subfolder = instance.subfolder
+
+ expect(subfolder).to be_a String
+ expect(instance.instance_variable_get(:@subfolder)).to eq subfolder
+ end
+
+ context "with an option given" do
+ let(:options) { { subfolder: "something" } }
+
+ it "uses the given path" do
+ expect(instance.subfolder).to eq "something"
+ end
+ end
+
+ context "without an option given" do
+ it "generates a path" do
+ expect(instance.subfolder).not_to be_blank
+ end
+ end
+ end
+
+ describe "#retention_dates" do
+ let(:retention_dates) { instance.retention_dates }
+
+ it "returns an array" do
+ expect(instance.retention_dates).to be_a Array
+ end
+
+ it "contains no duplicates" do
+ expect(retention_dates.size).to eq retention_dates.uniq.size
+ end
+ end
+
+ describe "#service" do
+ it "memoizes the storage service" do
+ allow(Fog::Storage).to receive(:new).and_return("__success__")
+
+ 2.times { instance.send(:service) }
+ expect(Fog::Storage).to have_received(:new).once
+ end
+ end
+end
diff --git a/spec/services/decidim/s3_sync_service_spec.rb b/spec/services/decidim/s3_sync_service_spec.rb
new file mode 100644
index 0000000000..ddc4c9d98b
--- /dev/null
+++ b/spec/services/decidim/s3_sync_service_spec.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Decidim::S3SyncService do
+ let(:temp_directory) { Dir.mktmpdir }
+ let(:options) { {} }
+ let(:instance) { described_class.new(options) }
+
+ after do
+ FileUtils.rm_rf temp_directory
+ end
+
+ describe ".run" do
+ it "executes the service" do
+ allow(described_class).to receive(:new).with(options).and_return instance
+ allow(instance).to receive(:execute).and_return "__success__"
+
+ expect(described_class.run({})).to eq "__success__"
+ end
+ end
+
+ describe "#default_options" do
+ let(:option_keys) { instance.default_options.keys }
+
+ it "returns a hash" do
+ expect(instance.default_options).to be_a Hash
+ end
+
+ it "has keys existing in backup configuration" do
+ config_keys = Rails.application.config.backup[:s3sync].keys
+ .map { |k| "s3_#{k}".to_sym }
+
+ aggregate_failures do
+ option_keys.filter { |k| k.match?(/^s3_/) }.each do |key|
+ expect(config_keys).to include key
+ end
+ end
+ end
+ end
+
+ describe "#has_local_backup_directory?" do
+ let(:options) { { local_backup_dir: temp_directory } }
+
+ context "when the directory exists and is readable" do
+ it "returns true" do
+ expect(instance.has_local_backup_directory?).to be true
+ end
+ end
+
+ context "when the directory does not exists" do
+ before { FileUtils.rm_rf temp_directory }
+
+ it "returns false" do
+ expect(instance.has_local_backup_directory?).to be false
+ end
+ end
+
+ context "when the directory exists but is not readable" do
+ before { FileUtils.chmod("ugo=wx", temp_directory) }
+
+ it "returns false" do
+ expect(instance.has_local_backup_directory?).to be false
+ end
+ end
+ end
+
+ describe "#subfolder" do
+ it "returns a memoized string" do
+ subfolder = instance.subfolder
+
+ expect(subfolder).to be_a String
+ expect(instance.instance_variable_get(:@subfolder)).to eq subfolder
+ end
+
+ context "with an option given" do
+ let(:options) { { subfolder: "something" } }
+
+ it "uses the given path" do
+ expect(instance.subfolder).to eq "something"
+ end
+ end
+
+ context "without an option given" do
+ it "generates a path" do
+ expect(instance.subfolder).not_to be_blank
+ end
+ end
+ end
+
+ describe "#force_upload?" do
+ context "when option was not provided" do
+ it "defaults to false" do
+ expect(instance.force_upload?).to be false
+ end
+ end
+
+ [true, false].each do |state|
+ context "when option was set to #{state}" do
+ let(:options) { { force_upload: state } }
+
+ it "returns #{state}" do
+ expect(instance.force_upload?).to be state
+ end
+ end
+ end
+ end
+
+ describe "#timestamp" do
+ it "returns a memoized string" do
+ timestamp = instance.timestamp
+
+ expect(timestamp).to be_a String
+ expect(instance.instance_variable_get(:@timestamp)).to eq timestamp
+ end
+ end
+
+ describe "#file_list" do
+ context "when no file list was provided" do
+ let(:options) { { local_backup_dir: temp_directory } }
+
+ before do
+ Dir.chdir(temp_directory) { `touch file_1.txt file_2.txt` }
+ end
+
+ it "reads from the backup directory" do
+ expected = [
+ "#{temp_directory}/file_1.txt",
+ "#{temp_directory}/file_2.txt"
+ ]
+ expect(instance.file_list.sort).to eq expected
+ end
+ end
+
+ context "when both a file list and a directory are provided" do
+ let(:options) do
+ {
+ local_backup_dir: temp_directory,
+ local_backup_files: %w(file_list_1.txt file_list_2.txt)
+ }
+ end
+
+ before do
+ Dir.chdir(temp_directory) { `touch file_1.txt file_2.txt` }
+ end
+
+ it "reads from the file list" do
+ expected = [
+ "file_list_1.txt",
+ "file_list_2.txt"
+ ]
+
+ expect(instance.file_list.sort).to eq expected
+ end
+ end
+ end
+
+ describe "#service" do
+ it "memoizes the storage service" do
+ allow(Fog::Storage).to receive(:new).and_return("__success__")
+
+ 2.times { instance.send(:service) }
+ expect(Fog::Storage).to have_received(:new).once
+ end
+ end
+end
diff --git a/spec/shared/initiative_administration_shared_context.rb b/spec/shared/initiative_administration_shared_context.rb
new file mode 100644
index 0000000000..abb44ddbf1
--- /dev/null
+++ b/spec/shared/initiative_administration_shared_context.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+shared_context "when admins initiative" do
+ let(:organization) { create(:organization) }
+ let(:user) { create(:user, :admin, :confirmed, organization: organization) }
+ let(:author) { create(:user, :confirmed, organization: organization) }
+ let(:other_initiatives_type) { create(:initiatives_type, organization: organization, signature_type: "any") }
+ let!(:other_initiatives_type_scope) { create(:initiatives_type_scope, type: other_initiatives_type) }
+
+ let(:initiative_type) { create(:initiatives_type, organization: organization) }
+ let(:initiative_scope) { create(:initiatives_type_scope, type: initiative_type) }
+ let!(:initiative) { create(:initiative, organization: organization, scoped_type: initiative_scope, author: author) }
+
+ let(:image1_filename) { "city.jpeg" }
+ 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) }
+end
diff --git a/spec/shared/update_initiative_answer_example.rb b/spec/shared/update_initiative_answer_example.rb
new file mode 100644
index 0000000000..5eab077786
--- /dev/null
+++ b/spec/shared/update_initiative_answer_example.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+shared_examples "update an initiative answer" do
+ let(:organization) { create(:organization) }
+ let(:initiative) { create(:initiative, organization: organization, state: state) }
+ let(:form) do
+ form_klass.from_params(
+ form_params
+ ).with_context(
+ current_organization: organization,
+ initiative: initiative
+ )
+ end
+ let(:signature_end_date) { Date.current + 500.days }
+ let(:state) { "published" }
+ let(:form_params) do
+ {
+ signature_start_date: Date.current + 10.days,
+ signature_end_date: signature_end_date,
+ answer: { en: "Measured answer" },
+ answer_url: "http://decidim.org"
+ }
+ end
+ let(:administrator) { create(:user, :admin, organization: organization) }
+ let(:current_user) { administrator }
+ let(:command) { described_class.new(initiative, form, current_user) }
+
+ describe "call" do
+ describe "when the form is not valid" do
+ before do
+ allow(form).to receive(:invalid?).and_return(true)
+ end
+
+ it "broadcasts invalid" do
+ expect { command.call }.to broadcast(:invalid)
+ end
+
+ it "doesn't updates the initiative" do
+ command.call
+
+ form_params.each do |key, value|
+ expect(initiative[key]).not_to eq(value)
+ end
+ end
+ end
+
+ describe "when the form is valid" do
+ it "broadcasts ok" do
+ expect { command.call }.to broadcast(:ok)
+ end
+
+ it "updates the initiative" do
+ command.call
+ initiative.reload
+
+ expect(initiative.answer["en"]).to eq(form_params[:answer][:en])
+ expect(initiative.answer_url).to eq(form_params[:answer_url])
+ end
+
+ context "when initiative is not published" do
+ let(:state) { "validating" }
+
+ it "voting interval remains unchanged" do
+ command.call
+ initiative.reload
+
+ [:signature_start_date, :signature_end_date].each do |key|
+ expect(initiative[key]).not_to eq(form_params[key])
+ end
+ end
+ end
+
+ context "when initiative is published" do
+ it "voting interval is updated" do
+ command.call
+ initiative.reload
+
+ [:signature_start_date, :signature_end_date].each do |key|
+ expect(initiative[key]).to eq(form_params[key])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d00ba7081b..af1e085061 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -9,6 +9,7 @@
RSpec.configure do |config|
config.formatter = ENV.fetch("RSPEC_FORMAT", "progress").to_sym
config.include EnvironmentVariablesHelper
+ config.include SkipIfUndefinedHelper
config.before do
# Initializers configs
diff --git a/spec/support/skip_if_undefined_helper.rb b/spec/support/skip_if_undefined_helper.rb
new file mode 100644
index 0000000000..adc421ffc7
--- /dev/null
+++ b/spec/support/skip_if_undefined_helper.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module SkipIfUndefinedHelper
+ # Skips a test if a given class is undefined (i.e. : from another gem)
+ def skip_if_undefined(klass, gem)
+ skip "'#{gem}' gem is not present" unless klass.safe_constantize
+ end
+end
diff --git a/spec/system/admin/update_initiative_spec.rb b/spec/system/admin/update_initiative_spec.rb
new file mode 100644
index 0000000000..ff65446f1b
--- /dev/null
+++ b/spec/system/admin/update_initiative_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "User prints the initiative", type: :system do
+ include_context "when admins initiative"
+
+ def submit_and_validate
+ find("*[type=submit]").click
+
+ within ".callout-wrapper" do
+ expect(page).to have_content("successfully")
+ end
+ end
+
+ context "when initiative update" do
+ context "and user is admin" do
+ before do
+ switch_to_host(organization.host)
+ login_as user, scope: :user
+ visit decidim_admin_initiatives.initiatives_path
+ end
+
+ it "Updates published initiative data" do
+ page.find(".action-icon--edit").click
+ within ".edit_initiative" do
+ fill_in :initiative_hashtag, with: "#hashtag"
+ end
+ submit_and_validate
+ end
+
+ context "when initiative is in created state" do
+ before do
+ initiative.created!
+ end
+
+ it "updates type, scope and signature type" do
+ page.find(".action-icon--edit").click
+ within ".edit_initiative" do
+ select translated(other_initiatives_type.title), from: "initiative_type_id"
+ select translated(other_initiatives_type_scope.scope.name), from: "initiative_decidim_scope_id"
+ select "In-person", from: "initiative_signature_type"
+ end
+ submit_and_validate
+ end
+
+ it "displays initiative attachments" do
+ page.find(".action-icon--edit").click
+ expect(page).to have_link("Edit")
+ expect(page).to have_link("New")
+ end
+ end
+
+ context "when initiative is in validating state" do
+ before do
+ initiative.validating!
+ end
+
+ it "updates type, scope and signature type" do
+ page.find(".action-icon--edit").click
+ within ".edit_initiative" do
+ select translated(other_initiatives_type.title), from: "initiative_type_id"
+ select translated(other_initiatives_type_scope.scope.name), from: "initiative_decidim_scope_id"
+ select "In-person", from: "initiative_signature_type"
+ end
+ submit_and_validate
+ end
+
+ it "displays initiative attachments" do
+ page.find(".action-icon--edit").click
+ expect(page).to have_link("Edit")
+ expect(page).to have_link("New")
+ end
+ end
+
+ context "when initiative is in accepted state" do
+ before do
+ initiative.accepted!
+ end
+
+ it "update of type, scope and signature type are disabled" do
+ page.find(".action-icon--edit").click
+
+ within ".edit_initiative" do
+ expect(page).to have_css("#initiative_type_id[disabled]")
+ expect(page).to have_css("#initiative_decidim_scope_id[disabled]")
+ expect(page).to have_css("#initiative_signature_type[disabled]")
+ end
+ end
+
+ it "displays initiative attachments" do
+ page.find(".action-icon--edit").click
+ expect(page).to have_link("Edit")
+ expect(page).to have_link("New")
+ end
+ end
+
+ context "when there is a single initiative type" do
+ let!(:other_initiatives_type) { nil }
+ let!(:other_initiatives_type_scope) { nil }
+
+ before do
+ initiative.created!
+ end
+
+ it "update of type, scope and signature type are disabled" do
+ page.find(".action-icon--edit").click
+
+ within ".edit_initiative" do
+ expect(page).not_to have_css("label[for='initiative_type_id']")
+ expect(page).not_to have_css("#initiative_type_id")
+
+ expect(page).to have_css("label[for='initiative_decidim_scope_id']")
+ expect(page).to have_css("#initiative_decidim_scope_id")
+ expect(page).to have_css("option[value='#{initiative_scope.id}'][selected='selected']")
+ expect(page).to have_css("label[for='initiative_signature_type']")
+ expect(page).to have_css("#initiative_signature_type")
+ expect(page).to have_css("option[value='#{initiative.signature_type}'][selected='selected']")
+ end
+ end
+ end
+ end
+ end
+end