From 41f8dbaff2bdeb8f256f9b96b3de8147b61dba05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 3 Sep 2020 08:38:57 +0200 Subject: [PATCH] Refactor custom fields into services --- .../concerns/requires_admin_guard.rb | 43 +++++ app/contracts/custom_fields/base_contract.rb | 53 ++++++ .../custom_fields/create_contract.rb | 34 ++++ .../custom_fields/update_contract.rb | 34 ++++ app/controllers/custom_fields_controller.rb | 46 +++--- app/services/custom_fields/create_service.rb | 62 +++++++ .../custom_fields/set_attributes_service.rb | 34 ++++ app/services/custom_fields/update_service.rb | 34 ++++ .../custom_fields/create_contract_spec.rb | 56 +++++++ .../custom_fields/update_contract_spec.rb | 56 +++++++ .../custom_fields/create_service_spec.rb | 155 ++++++++++++++++++ .../set_attributes_service_spec.rb | 126 ++++++++++++++ .../custom_fields/update_service_spec.rb | 152 +++++++++++++++++ 13 files changed, 863 insertions(+), 22 deletions(-) create mode 100644 app/contracts/concerns/requires_admin_guard.rb create mode 100644 app/contracts/custom_fields/base_contract.rb create mode 100644 app/contracts/custom_fields/create_contract.rb create mode 100644 app/contracts/custom_fields/update_contract.rb create mode 100644 app/services/custom_fields/create_service.rb create mode 100644 app/services/custom_fields/set_attributes_service.rb create mode 100644 app/services/custom_fields/update_service.rb create mode 100644 spec/contracts/custom_fields/create_contract_spec.rb create mode 100644 spec/contracts/custom_fields/update_contract_spec.rb create mode 100644 spec/services/custom_fields/create_service_spec.rb create mode 100644 spec/services/custom_fields/set_attributes_service_spec.rb create mode 100644 spec/services/custom_fields/update_service_spec.rb diff --git a/app/contracts/concerns/requires_admin_guard.rb b/app/contracts/concerns/requires_admin_guard.rb new file mode 100644 index 00000000000..85b08d381b8 --- /dev/null +++ b/app/contracts/concerns/requires_admin_guard.rb @@ -0,0 +1,43 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module RequiresAdminGuard + extend ActiveSupport::Concern + + included do + validate :validate_admin_only + + def validate_admin_only + unless user.admin? + errors.add :base, :error_unauthorized + end + end + end +end diff --git a/app/contracts/custom_fields/base_contract.rb b/app/contracts/custom_fields/base_contract.rb new file mode 100644 index 00000000000..88231488ff8 --- /dev/null +++ b/app/contracts/custom_fields/base_contract.rb @@ -0,0 +1,53 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class BaseContract < ::ModelContract + include RequiresAdminGuard + + attribute :editable + attribute :type + attribute :field_format + attribute :is_filter + attribute :is_for_all + attribute :is_required + attribute :max_length + attribute :min_length + attribute :name + attribute :possible_values + attribute :regexp + attribute :searchable + attribute :visible + attribute :default_value + attribute :possible_values + attribute :multi_value + attribute :content_right_to_left + end +end diff --git a/app/contracts/custom_fields/create_contract.rb b/app/contracts/custom_fields/create_contract.rb new file mode 100644 index 00000000000..9784c09276e --- /dev/null +++ b/app/contracts/custom_fields/create_contract.rb @@ -0,0 +1,34 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class CreateContract < BaseContract + end +end diff --git a/app/contracts/custom_fields/update_contract.rb b/app/contracts/custom_fields/update_contract.rb new file mode 100644 index 00000000000..f096294896c --- /dev/null +++ b/app/contracts/custom_fields/update_contract.rb @@ -0,0 +1,34 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class UpdateContract < BaseContract + end +end diff --git a/app/controllers/custom_fields_controller.rb b/app/controllers/custom_fields_controller.rb index b3cdbaa3430..b3fb38376cc 100644 --- a/app/controllers/custom_fields_controller.rb +++ b/app/controllers/custom_fields_controller.rb @@ -47,17 +47,26 @@ class CustomFieldsController < ApplicationController end def new - @custom_field = careful_new_custom_field permitted_params.custom_field_type + @custom_field = new_custom_field + + if @custom_field.nil? + flash[:error] = 'Invalid CF type' + redirect_to action: :index + end end def create - @custom_field = careful_new_custom_field permitted_params.custom_field_type, get_custom_field_params + call = ::CustomFields::CreateService + .new(user: current_user) + .call(get_custom_field_params.merge(type: permitted_params.custom_field_type)) - if @custom_field.save - flash[:notice] = l(:notice_successful_create) - call_hook(:controller_custom_fields_new_after_save, custom_field: @custom_field) - redirect_to custom_fields_path(tab: @custom_field.class.name) + if call.success? + flash[:notice] = t(:notice_successful_create) + call_hook(:controller_custom_fields_new_after_save, custom_field: call.result) + redirect_to custom_fields_path(tab: call.result.class.name) else + flash[:error] = call.message || I18n.t('notice_internal_server_error') + @custom_field = call.result || new_custom_field render action: 'new' end end @@ -65,13 +74,16 @@ class CustomFieldsController < ApplicationController def edit; end def update - @custom_field.attributes = get_custom_field_params + call = ::CustomFields::UpdateService + .new(user: current_user, model: @custom_field) + .call(get_custom_field_params) - if @custom_field.save + if call.success? flash[:notice] = t(:notice_successful_update) call_hook(:controller_custom_fields_edit_after_save, custom_field: @custom_field) redirect_back_or_default edit_custom_field_path(id: @custom_field.id) else + flash[:error] = call.message || I18n.t('notice_internal_server_error') render action: 'edit' end end @@ -101,6 +113,10 @@ class CustomFieldsController < ApplicationController private + def new_custom_field + ::CustomFields::CreateService.careful_new_custom_field(permitted_params.custom_field_type) + end + def get_custom_field_params custom_field_params = permitted_params.custom_field @@ -133,20 +149,6 @@ class CustomFieldsController < ApplicationController end end - def careful_new_custom_field(type, params = {}) - cf = begin - if type.to_s =~ /.+CustomField\z/ - klass = type.to_s.constantize - klass.new(params) if klass.ancestors.include? CustomField - end - rescue NameError => e - Rails.logger.error "#{e.message}:\n#{e.backtrace.join("\n")}" - nil - end - redirect_to custom_fields_path unless cf - cf - end - def find_custom_field @custom_field = CustomField.find(params[:id]) rescue ActiveRecord::RecordNotFound diff --git a/app/services/custom_fields/create_service.rb b/app/services/custom_fields/create_service.rb new file mode 100644 index 00000000000..2c30093331a --- /dev/null +++ b/app/services/custom_fields/create_service.rb @@ -0,0 +1,62 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class CreateService < ::BaseServices::Create + def self.careful_new_custom_field(type) + if type.to_s =~ /.+CustomField\z/ + klass = type.to_s.constantize + klass.new if klass.ancestors.include? CustomField + end + rescue NameError => e + Rails.logger.error "#{e.message}:\n#{e.backtrace.join("\n")}" + nil + end + + def perform(params) + super + rescue StandardError => e + ServiceResult.new(success: false, message: e.message) + end + + def instance(params) + cf = self.class.careful_new_custom_field(params[:type]) + raise ArgumentError.new("Invalid CF type") unless cf + + cf + end + + private + + def add_cf_to_visible_columns(id) + Setting.enabled_projects_columns = (Setting.enabled_projects_columns + ["cf_#{id}"]).uniq + end + end +end diff --git a/app/services/custom_fields/set_attributes_service.rb b/app/services/custom_fields/set_attributes_service.rb new file mode 100644 index 00000000000..5c83ee9bcef --- /dev/null +++ b/app/services/custom_fields/set_attributes_service.rb @@ -0,0 +1,34 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class SetAttributesService < ::BaseServices::SetAttributes + end +end diff --git a/app/services/custom_fields/update_service.rb b/app/services/custom_fields/update_service.rb new file mode 100644 index 00000000000..3e626a35052 --- /dev/null +++ b/app/services/custom_fields/update_service.rb @@ -0,0 +1,34 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomFields + class UpdateService < ::BaseServices::Update + end +end diff --git a/spec/contracts/custom_fields/create_contract_spec.rb b/spec/contracts/custom_fields/create_contract_spec.rb new file mode 100644 index 00000000000..6fb24be95ec --- /dev/null +++ b/spec/contracts/custom_fields/create_contract_spec.rb @@ -0,0 +1,56 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe CustomFields::CreateContract do + let(:cf) { FactoryBot.build :project_custom_field } + let(:contract) do + described_class.new(cf, current_user, options: { changed_by_system: [] }) + end + + describe 'as admin' do + let(:current_user) { FactoryBot.build_stubbed :admin } + + it 'validates the contract' do + expect(contract.validate).to eq(true) + end + end + + describe 'as regular user' do + let(:current_user) { FactoryBot.build_stubbed :user } + + it 'invalidates the contract' do + expect(contract.validate).to eq(false) + expect(contract.errors.symbols_for(:base)) + .to match_array [:error_unauthorized] + end + end +end diff --git a/spec/contracts/custom_fields/update_contract_spec.rb b/spec/contracts/custom_fields/update_contract_spec.rb new file mode 100644 index 00000000000..3e10fd721f1 --- /dev/null +++ b/spec/contracts/custom_fields/update_contract_spec.rb @@ -0,0 +1,56 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe CustomFields::UpdateContract do + let(:cf) { FactoryBot.build :project_custom_field } + let(:contract) do + described_class.new(cf, current_user, options: { changed_by_system: [] }) + end + + describe 'as admin' do + let(:current_user) { FactoryBot.build_stubbed :admin } + + it 'validates the contract' do + expect(contract.validate).to eq(true) + end + end + + describe 'as regular user' do + let(:current_user) { FactoryBot.build_stubbed :user } + + it 'invalidates the contract' do + expect(contract.validate).to eq(false) + expect(contract.errors.symbols_for(:base)) + .to match_array [:error_unauthorized] + end + end +end diff --git a/spec/services/custom_fields/create_service_spec.rb b/spec/services/custom_fields/create_service_spec.rb new file mode 100644 index 00000000000..085e442fe55 --- /dev/null +++ b/spec/services/custom_fields/create_service_spec.rb @@ -0,0 +1,155 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe CustomFields::CreateService, type: :model do + let(:user) { FactoryBot.build_stubbed(:user) } + let(:contract_class) do + double('contract_class', '<=': true) + end + let(:cf_valid) { true } + let(:instance) do + described_class.new(user: user, contract_class: contract_class) + end + let(:call_attributes) { { type: 'ProjectCustomField', name: 'Some name', field_format: 'string' } } + let(:set_attributes_success) do + true + end + let(:set_attributes_errors) do + double('set_attributes_errors') + end + let(:set_attributes_result) do + ServiceResult.new result: created_cf, + success: set_attributes_success, + errors: set_attributes_errors + end + let!(:created_cf) do + cf = FactoryBot.build_stubbed(:project_custom_field) + + allow(ProjectCustomField) + .to receive(:new) + .and_return(cf) + + allow(cf) + .to receive(:save) + .and_return(cf_valid) + + cf + end + + let!(:set_attributes_service) do + service = double('set_attributes_service_instance') + + allow(CustomFields::SetAttributesService) + .to receive(:new) + .with(user: user, + model: created_cf, + contract_class: contract_class, + contract_options: {}) + .and_return(service) + + allow(service) + .to receive(:call) + .and_return(set_attributes_result) + end + let(:new_project_role) { FactoryBot.build_stubbed(:role) } + + describe 'call' do + subject { instance.call(call_attributes) } + + it 'is successful' do + expect(subject.success?).to be_truthy + end + + it 'returns the result of the SetAttributesService' do + expect(subject) + .to eql set_attributes_result + end + + it 'persists the custom_field' do + expect(created_cf) + .to receive(:save) + .and_return(cf_valid) + + subject + end + + it 'modifies the settings' do + subject + expect(Setting.enabled_projects_columns).to include "cf_#{created_cf.id}" + end + + it 'creates the custom field' do + expect(subject.result).to eql created_cf + expect(subject.result).to be_kind_of(ProjectCustomField) + end + + context 'if the SetAttributeService is unsuccessful' do + let(:set_attributes_success) { false } + + it 'is unsuccessful' do + expect(subject.success?).to be_falsey + end + + it 'returns the result of the SetAttributesService' do + expect(subject) + .to eql set_attributes_result + end + + it 'does not persist the changes' do + expect(created_cf) + .to_not receive(:save) + + subject + end + + it "exposes the contract's errors" do + subject + + expect(subject.errors).to eql set_attributes_errors + end + end + + context 'when the cf is invalid' do + let(:cf_valid) { false } + + it 'is unsuccessful' do + expect(subject.success?).to be_falsey + end + + it "exposes the message's errors" do + subject + + expect(subject.errors).to eql created_cf.errors + end + end + end +end diff --git a/spec/services/custom_fields/set_attributes_service_spec.rb b/spec/services/custom_fields/set_attributes_service_spec.rb new file mode 100644 index 00000000000..cb6df85f7f1 --- /dev/null +++ b/spec/services/custom_fields/set_attributes_service_spec.rb @@ -0,0 +1,126 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe CustomFields::SetAttributesService, type: :model do + let(:user) { FactoryBot.build_stubbed(:user) } + let(:contract_instance) do + contract = double('contract_instance') + allow(contract) + .to receive(:validate) + .and_return(contract_valid) + allow(contract) + .to receive(:errors) + .and_return(contract_errors) + contract + end + + let(:contract_errors) { double('contract_errors') } + let(:contract_valid) { true } + let(:cf_valid) { true } + + let(:instance) do + described_class.new(user: user, + model: cf_instance, + contract_class: contract_class, + contract_options: {}) + end + let(:cf_instance) { ProjectCustomField.new } + let(:contract_class) do + allow(CustomFields::CreateContract) + .to receive(:new) + .with(cf_instance, user, options: { changed_by_system: [] }) + .and_return(contract_instance) + + CustomFields::CreateContract + end + + let(:params) { {} } + + before do + allow(cf_instance) + .to receive(:valid?) + .and_return(cf_valid) + end + + subject { instance.call(params) } + + it 'returns the cf instance as the result' do + expect(subject.result) + .to eql cf_instance + end + + it 'is a success' do + is_expected + .to be_success + end + + context 'with params' do + let(:params) do + { + name: 'Foobar' + } + end + + let(:expected) do + { + name: 'Foobar' + }.with_indifferent_access + end + + it 'assigns the params' do + subject + + attributes_of_interest = cf_instance + .attributes + .slice(*expected.keys) + + expect(attributes_of_interest) + .to eql(expected) + end + end + + context 'with an invalid contract' do + let(:contract_valid) { false } + let(:expect_time_instance_save) do + expect(cf_instance) + .not_to receive(:save) + end + + it 'returns failure' do + is_expected + .not_to be_success + end + + it "returns the contract's errors" do + expect(subject.errors) + .to eql(contract_errors) + end + end +end diff --git a/spec/services/custom_fields/update_service_spec.rb b/spec/services/custom_fields/update_service_spec.rb new file mode 100644 index 00000000000..016246e8db1 --- /dev/null +++ b/spec/services/custom_fields/update_service_spec.rb @@ -0,0 +1,152 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2020 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe CustomFields::UpdateService, type: :model do + let(:user) { FactoryBot.build_stubbed(:user) } + let(:contract_class) do + double('contract_class', "<=": true) + end + let(:cf_valid) { true } + let(:instance) do + described_class.new(user: user, + model: cf, + contract_class: contract_class, + contract_options: {}) + end + let(:call_attributes) { {} } + let(:set_attributes_success) do + true + end + let(:set_attributes_errors) do + double('set_attributes_errors') + end + let(:set_attributes_result) do + ServiceResult.new result: cf, + success: set_attributes_success, + errors: set_attributes_errors + end + let!(:cf) do + cf = FactoryBot.build_stubbed(:project_custom_field) + + allow(cf) + .to receive(:save) + .and_return(cf_valid) + + cf + end + let!(:set_attributes_service) do + service = double('set_attributes_service_instance') + + allow(CustomFields::SetAttributesService) + .to receive(:new) + .with(user: user, + model: cf, + contract_class: contract_class, + contract_options: {}) + .and_return(service) + + allow(service) + .to receive(:call) + .and_return(set_attributes_result) + + service + end + + describe 'call' do + shared_examples_for 'service call' do + subject { instance.call(call_attributes) } + + it 'is successful' do + expect(subject.success?).to be_truthy + end + + it 'returns the result of the SetAttributesService' do + expect(subject) + .to eql set_attributes_result + end + + it 'persists the custom field' do + expect(cf) + .to receive(:save) + .and_return(cf_valid) + + subject + end + + context 'when the SetAttributeService is unsuccessful' do + let(:set_attributes_success) { false } + + it 'is unsuccessful' do + expect(subject.success?).to be_falsey + end + + it 'returns the result of the SetAttributesService' do + expect(subject) + .to eql set_attributes_result + end + + it 'does not persist the changes' do + expect(cf) + .to_not receive(:save) + + subject + end + + it "exposes the contract's errors" do + subject + + expect(subject.errors).to eql set_attributes_errors + end + end + + context 'when the custom field is invalid' do + let(:cf_valid) { false } + + it 'is unsuccessful' do + expect(subject.success?).to be_falsey + end + + it "exposes the custom field's errors" do + subject + + expect(subject.errors).to eql cf.errors + end + end + end + + context 'with parameters' do + let(:call_attributes) { { sticky: true } } + + it_behaves_like 'service call' + end + end +end