bail on inexistent related resource

* status
* type
* assignee
* priority
pull/7893/head
ulferts 5 years ago
parent 78722e8dbe
commit bc682f6b01
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 28
      app/contracts/work_packages/base_contract.rb
  2. 31
      app/models/priority/inexistent_priority.rb
  3. 31
      app/models/status/inexistent_status.rb
  4. 32
      app/models/type/inexistent_type.rb
  5. 31
      app/models/user/inexistent_user.rb
  6. 2
      app/services/work_packages/set_attributes_service.rb
  7. 4
      modules/bcf/app/representers/bcf/api/v2_1/errors/error_mapper.rb
  8. 74
      modules/bcf/app/services/bcf/issues/transform_attributes_service.rb
  9. 64
      modules/bcf/spec/requests/api/bcf/v2_1/topics_api_spec.rb
  10. 56
      spec/contracts/work_packages/base_contract_spec.rb

@ -103,7 +103,9 @@ module WorkPackages
message: :greater_than_or_equal_to_start_date,
allow_blank: true },
unless: Proc.new { |wp| wp.start_date.blank? }
validate :validate_enabled_type
validate :validate_type_exists
validate :validate_milestone_constraint
validate :validate_parent_not_milestone
@ -112,13 +114,17 @@ module WorkPackages
validate :validate_parent_in_same_project
validate :validate_parent_not_subtask
validate :validate_status_exists
validate :validate_status_transition
validate :validate_active_priority
validate :validate_priority_exists
validate :validate_category
validate :validate_estimated_hours
validate :validate_assigned_to_exists
def initialize(work_package, user, options: {})
super
@ -149,11 +155,19 @@ module WorkPackages
def validate_enabled_type
# Checks that the issue can not be added/moved to a disabled type
if model.project && (model.type_id_changed? || model.project_id_changed?)
if model.project && !type_inexistent? && (model.type_id_changed? || model.project_id_changed?)
errors.add :type_id, :inclusion unless model.project.types.include?(model.type)
end
end
def validate_assigned_to_exists
errors.add :assigned_to, :does_not_exist if model.assigned_to&.is_a?(User::InexistentUser)
end
def validate_type_exists
errors.add :type, :does_not_exist if type_inexistent?
end
def validate_milestone_constraint
if model.is_milestone? && model.due_date && model.start_date && model.start_date != model.due_date
errors.add :due_date, :not_start_date
@ -186,6 +200,10 @@ module WorkPackages
end
end
def validate_status_exists
errors.add :status, :does_not_exist if model.status&.is_a?(Status::InexistentStatus)
end
def validate_status_transition
if status_changed? && status_exists? && !(model.type_id_changed? || status_transition_exists?)
errors.add :status_id, :status_transition_invalid
@ -198,6 +216,10 @@ module WorkPackages
end
end
def validate_priority_exists
errors.add :priority, :does_not_exist if model.priority&.is_a?(Priority::InexistentPriority)
end
def validate_category
if inexistent_category?
errors.add :category, :does_not_exist
@ -287,5 +309,9 @@ module WorkPackages
query
end
end
def type_inexistent?
model.type.is_a?(Type::InexistentType)
end
end
end

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# 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.
#++
class Priority::InexistentPriority < IssuePriority; end

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# 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.
#++
class Status::InexistentStatus < Status; end

@ -0,0 +1,32 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# 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.
#++
class Type::InexistentType < Type
end

@ -0,0 +1,31 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# 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.
#++
class User::InexistentUser < User; end

@ -177,7 +177,7 @@ class WorkPackages::SetAttributesService < ::BaseServices::SetAttributes
end
def reassign_status(available_statuses)
return if available_statuses.include? work_package.status
return if available_statuses.include?(work_package.status) || work_package.status.is_a?(Status::InexistentStatus)
new_status = available_statuses.detect(&:is_default) || available_statuses.first
work_package.status = new_status if new_status.present?

@ -53,6 +53,10 @@ module Bcf::API::V2_1::Errors
mapped_errors
end
def self.i18n_scope
:activerecord
end
def self.error_key_mapper(key)
{ subject: :title }[key] || key
end

@ -52,7 +52,13 @@ module Bcf::Issues
end
def assignee(project, attributes)
find_user_in_project(project, attributes[:assignee])
assignee = find_user_in_project(project, attributes[:assignee])
return assignee if assignee.present?
if attributes[:assignee]
User::InexistentUser.new
end
end
##
@ -67,16 +73,7 @@ module Bcf::Issues
return type if type.present?
import_options = attributes[:import_options]
return unless import_options
if import_options[:unknown_types_action] == 'default'
::Type.default&.first
elsif import_options[:unknown_types_action] == 'chose' &&
import_options[:unknown_types_chose_ids].any?
::Type.find_by(id: import_options[:unknown_types_chose_ids].first)
end
missing_type(type_name, attributes[:import_options] || {})
end
##
@ -87,16 +84,7 @@ module Bcf::Issues
return status if status.present?
import_options = attributes[:import_options]
return unless import_options
if import_options[:unknown_statuses_action] == 'use_default'
::Status.default
elsif import_options[:unknown_statuses_action] == 'chose' &&
import_options[:unknown_statuses_chose_ids].any?
::Status.find_by(id: import_options[:unknown_statuses_chose_ids].first)
end
missing_status(status_name, attributes[:import_options] || {})
end
##
@ -107,16 +95,7 @@ module Bcf::Issues
return priority if priority.present?
import_options = attributes[:import_options]
return unless import_options
if import_options[:unknown_priorities_action] == 'use_default'
# NOP The 'use_default' case gets already covered by OP.
elsif import_options[:unknown_priorities_action] == 'chose' &&
import_options[:unknown_priorities_chose_ids].any?
::IssuePriority.find_by(id: import_options[:unknown_priorities_chose_ids].first)
end
missing_priority(priority_name, attributes[:import_options] || {})
end
##
@ -142,5 +121,38 @@ module Bcf::Issues
priority: priority(attributes)
}.compact
end
def missing_status(status_name, import_options)
if import_options[:unknown_statuses_action] == 'use_default'
::Status.default
elsif import_options[:unknown_statuses_action] == 'chose' &&
import_options[:unknown_statuses_chose_ids].any?
::Status.find_by(id: import_options[:unknown_statuses_chose_ids].first)
elsif status_name
Status::InexistentStatus.new
end
end
def missing_priority(priority_name, import_options)
if import_options[:unknown_priorities_action] == 'use_default'
# NOP The 'use_default' case gets already covered by OP.
elsif import_options[:unknown_priorities_action] == 'chose' &&
import_options[:unknown_priorities_chose_ids].any?
::IssuePriority.find_by(id: import_options[:unknown_priorities_chose_ids].first)
elsif priority_name
Priority::InexistentPriority.new
end
end
def missing_type(type_name, import_options)
if import_options[:unknown_types_action] == 'default'
::Type.default&.first
elsif import_options[:unknown_types_action] == 'chose' &&
import_options[:unknown_types_chose_ids].any?
::Type.find_by(id: import_options[:unknown_types_chose_ids].first)
elsif type_name
Type::InexistentType.new
end
end
end
end

@ -90,6 +90,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
@ -138,6 +139,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
@ -294,7 +296,67 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Title can't be blank"
"Title can't be blank."
end
end
end
context 'with an inexistent status' do
let(:params) do
{
title: 'Some title',
topic_status: 'Some non existing status'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Status does not exist."
end
end
end
context 'with an inexistent priority' do
let(:params) do
{
title: 'Some title',
priority: 'Some non existing priority'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Priority does not exist."
end
end
end
context 'with an inexistent type' do
let(:params) do
{
title: 'Some title',
topic_type: 'Some non existing type'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Type does not exist."
end
end
end
context 'with an inexistent assigned_to' do
let(:params) do
{
title: 'Some title',
assigned_to: 'Some non existing assignee'
}
end
it_behaves_like 'bcf api unprocessable response' do
let(:message) do
"Assignee does not exist."
end
end
end

@ -174,7 +174,7 @@ describe WorkPackages::BaseContract do
before do
allow(work_package)
.to receive(:status_id_change)
.and_return [1,2]
.and_return [1, 2]
end
it 'is writable' do
@ -182,6 +182,19 @@ describe WorkPackages::BaseContract do
end
end
end
context 'is an inexistent status' do
before do
work_package.status = Status::InexistentStatus.new
end
it 'is invalid' do
contract.validate
expect(subject.errors.symbols_for(:status))
.to match_array [:does_not_exist]
end
end
end
describe 'estimated hours' do
@ -521,6 +534,34 @@ describe WorkPackages::BaseContract do
end
end
end
context 'inexistent type' do
before do
work_package.type = Type::InexistentType.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:type))
.to match_array [:does_not_exist]
end
end
end
context 'assigned_to' do
context 'inexistent user' do
before do
work_package.assigned_to = User::InexistentUser.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:assigned_to))
.to match_array [:does_not_exist]
end
end
end
describe 'category' do
@ -630,6 +671,19 @@ describe WorkPackages::BaseContract do
.to be_empty
end
end
context 'inexistent priority' do
before do
work_package.priority = Priority::InexistentPriority.new
contract.validate
end
it 'is invalid' do
expect(contract.errors.symbols_for(:priority))
.to match_array [:does_not_exist]
end
end
end
describe 'status' do

Loading…
Cancel
Save