Merge pull request #3129 from ulferts/feature/create_work_package_for_release_4_2

Feature/create work package for release 4 2
pull/3130/head
ulferts 10 years ago
commit 159482b378
  1. 6
      app/controllers/work_packages_controller.rb
  2. 52
      app/services/create_work_package_service.rb
  3. 11
      app/services/update_work_package_service.rb
  4. 25
      lib/api/contracts/model_contract.rb
  5. 4
      lib/api/decorators/single.rb
  6. 6
      lib/api/utilities/text_renderer.rb
  7. 36
      lib/api/v3/activities/activity_representer.rb
  8. 14
      lib/api/v3/projects/project_representer.rb
  9. 3
      lib/api/v3/projects/projects_api.rb
  10. 7
      lib/api/v3/relations/relation_representer.rb
  11. 2
      lib/api/v3/render/render_api.rb
  12. 7
      lib/api/v3/users/user_representer.rb
  13. 8
      lib/api/v3/utilities/path_helper.rb
  14. 23
      lib/api/v3/work_packages/base_contract.rb
  15. 26
      lib/api/v3/work_packages/create_contract.rb
  16. 47
      lib/api/v3/work_packages/create_form_api.rb
  17. 67
      lib/api/v3/work_packages/create_form_representer.rb
  18. 113
      lib/api/v3/work_packages/form/form_representer.rb
  19. 119
      lib/api/v3/work_packages/form/work_package_attribute_links_representer.rb
  20. 157
      lib/api/v3/work_packages/form/work_package_payload_representer.rb
  21. 64
      lib/api/v3/work_packages/form_representer.rb
  22. 8
      lib/api/v3/work_packages/schema/work_package_schema_representer.rb
  23. 59
      lib/api/v3/work_packages/update_contract.rb
  24. 47
      lib/api/v3/work_packages/update_form_api.rb
  25. 67
      lib/api/v3/work_packages/update_form_representer.rb
  26. 118
      lib/api/v3/work_packages/work_package_attribute_links_representer.rb
  27. 147
      lib/api/v3/work_packages/work_package_payload_representer.rb
  28. 44
      lib/api/v3/work_packages/work_package_representer.rb
  29. 64
      lib/api/v3/work_packages/work_packages_api.rb
  30. 69
      lib/api/v3/work_packages/work_packages_by_project_api.rb
  31. 99
      lib/api/v3/work_packages/work_packages_shared_helpers.rb
  32. 73
      spec/app/services/create_work_package_service_spec.rb
  33. 61
      spec/decorators/single_spec.rb
  34. 88
      spec/lib/api/contracts/model_contract_spec.rb
  35. 45
      spec/lib/api/v3/projects/project_representer_spec.rb
  36. 16
      spec/lib/api/v3/utilities/path_helper_spec.rb
  37. 2
      spec/lib/api/v3/work_packages/base_contract_spec.rb
  38. 119
      spec/lib/api/v3/work_packages/create_form_representer_spec.rb
  39. 82
      spec/lib/api/v3/work_packages/form_representer_spec.rb
  40. 68
      spec/lib/api/v3/work_packages/update_contract_spec.rb
  41. 35
      spec/lib/api/v3/work_packages/update_form_representer_spec.rb
  42. 2
      spec/lib/api/v3/work_packages/work_package_payload_representer_spec.rb
  43. 103
      spec/lib/api/v3/work_packages/work_packages_shared_helpers_spec.rb
  44. 31
      spec/requests/api/v3/render_resource_spec.rb
  45. 17
      spec/requests/api/v3/work_package_resource_spec.rb
  46. 54
      spec/requests/api/v3/work_packages/create_form_resource_spec.rb
  47. 148
      spec/requests/api/v3/work_packages/work_packages_by_project_resource_spec.rb

@ -177,7 +177,11 @@ class WorkPackagesController < ApplicationController
def update
safe_params = permitted_params.update_work_package(project: project)
update_service = UpdateWorkPackageService.new(current_user, work_package, safe_params, send_notifications?)
update_service = UpdateWorkPackageService.new(
user: current_user,
work_package: work_package,
permitted_params: safe_params,
send_notifications: send_notifications?)
updated = update_service.update

@ -0,0 +1,52 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
class CreateWorkPackageService
attr_accessor :user, :project, :work_package
def initialize(user:, project:, send_notifications: true)
self.user = user
self.project = project
WorkPackageObserver.instance.send_notification = send_notifications
end
def create
hash = {
project: project,
author: user,
type: project.types.where(is_default: true).first || project.types.first
}
self.work_package = project.add_work_package(hash)
end
def save(work_package)
work_package.save
end
end

@ -28,15 +28,14 @@
#++
class UpdateWorkPackageService
attr_accessor :user, :work_package, :permitted_params, :send_notifications
attr_accessor :user, :work_package, :permitted_params
def initialize(user, work_package, permitted_params = nil, send_notifications = true)
def initialize(user:, work_package:, permitted_params: nil, send_notifications: true)
self.user = user
self.work_package = work_package
self.permitted_params = permitted_params
self.send_notifications = send_notifications
configure_update_notification
JournalObserver.instance.send_notification = send_notifications
end
def update
@ -49,10 +48,6 @@ class UpdateWorkPackageService
private
def configure_update_notification
JournalObserver.instance.send_notification = send_notifications
end
def effective_params
effective_params = HashWithIndifferentAccess.new

@ -48,19 +48,40 @@ module API
end
end
def writable_attributes
collect_ancestor_attributes(:writable_attributes)
end
validate :readonly_attributes_unchanged
validate :run_attribute_validations
private
def readonly_attributes_unchanged
changed_attributes = model.changed - self.class.writable_attributes
changed_attributes = model.changed - writable_attributes
errors.add :error_readonly, changed_attributes unless changed_attributes.empty?
end
def run_attribute_validations
self.class.attribute_validations.each { |validation| instance_exec(&validation) }
attribute_validations.each { |validation| instance_exec(&validation) }
end
def attribute_validations
collect_ancestor_attributes(:attribute_validations)
end
# Traverse ancestor hierarchy to collect contract information.
# This allows to define attributes on a common base class of two or more contracts.
def collect_ancestor_attributes(attribute_to_collect)
attributes = []
klass = self.class
while klass != ModelContract
# Collect all the attribute_to_collect from ancestors
attributes += klass.send(attribute_to_collect)
klass = klass.superclass
end
attributes.uniq
end
end
end

@ -100,6 +100,10 @@ module API
if: show_if
end
def current_user_allowed_to(permission, context:)
current_user && current_user.allowed_to?(permission, context)
end
protected
def current_user

@ -39,7 +39,11 @@ module API
@text = text
@format = format
@object = object
@project = object.project if object.respond_to?(:project)
if object.respond_to?(:project)
@project = object.project
elsif @object.is_a?(Project)
@project = object
end
end
def to_html

@ -27,36 +27,16 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
API::V3::Utilities::DateTimeFormatter
module API
module V3
module Activities
class ActivityRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
include API::V3::Utilities::PathHelper
class ActivityRepresenter < ::API::Decorators::Single
include API::V3::Utilities
self.as_strategy = API::Utilities::CamelCasingStrategy.new
def initialize(model, options = {})
@current_user = options[:current_user]
super(model)
end
property :_type, exec_context: :decorator
link :self do
{
href: api_v3_paths.activity(represented.id),
title: "#{represented.id}"
}
end
self_link path: :activity,
title_getter: -> (*) { nil }
link :workPackage do
{
@ -113,11 +93,11 @@ module API
private
def current_user_allowed_to_edit?
(current_user_allowed_to(:edit_own_work_package_notes, represented.journable) && represented.editable_by?(@current_user)) || current_user_allowed_to(:edit_work_package_notes, represented.journable)
end
def current_user_allowed_to(permission, work_package)
@current_user && @current_user.allowed_to?(permission, work_package.project)
(current_user_allowed_to(:edit_own_work_package_notes,
context: represented.journable.project) &&
represented.editable_by?(current_user)) ||
current_user_allowed_to(:edit_work_package_notes,
context: represented.journable.project)
end
def render_details(journal, no_html: false)

@ -37,6 +37,20 @@ module API
self_link
link :createWorkPackage do
{
href: api_v3_paths.create_work_package_form(represented.id),
method: :post
} if current_user_allowed_to(:add_work_packages, context: represented)
end
link :createWorkPackageImmediate do
{
href: api_v3_paths.work_packages_by_project(represented.id),
method: :post
} if current_user_allowed_to(:add_work_packages, context: represented)
end
link 'categories' do
{ href: api_v3_paths.categories(represented.id) }
end

@ -47,11 +47,12 @@ module API
end
get do
ProjectRepresenter.new(@project)
ProjectRepresenter.new(@project, current_user: current_user)
end
mount API::V3::Projects::AvailableAssigneesAPI
mount API::V3::Projects::AvailableResponsiblesAPI
mount API::V3::WorkPackages::WorkPackagesByProjectAPI
mount API::V3::Categories::CategoriesByProjectAPI
mount API::V3::Versions::VersionsByProjectAPI
mount API::V3::Types::TypesByProjectAPI

@ -51,7 +51,8 @@ module API
href: api_v3_paths.work_package_relation(represented.id, represented.from.id),
method: :delete,
title: 'Remove relation'
} if current_user_allowed_to(:manage_work_package_relations)
} if current_user_allowed_to(:manage_work_package_relations,
context: represented.from.project)
end
property :delay, render_nil: true, if: -> (*) { relation_type == 'precedes' }
@ -62,10 +63,6 @@ module API
private
def current_user_allowed_to(permission)
current_user && current_user.allowed_to?(permission, represented.from.project)
end
def relation_type
represented.relation_type_for(work_package).camelize
end

@ -36,7 +36,7 @@ module API
resources :render do
helpers do
SUPPORTED_CONTEXT_NAMESPACES = ['work_packages'].freeze
SUPPORTED_CONTEXT_NAMESPACES = %w(work_packages projects).freeze
SUPPORTED_MEDIA_TYPE = 'text/plain'
def check_content_type

@ -67,7 +67,8 @@ module API
href: api_v3_paths.watcher(represented.id, work_package.id),
method: :delete,
title: 'Remove watcher'
} if work_package && current_user_allowed_to(:delete_work_package_watchers, work_package)
} if work_package && current_user_allowed_to(:delete_work_package_watchers,
context: work_package.project)
end
property :id,
@ -114,10 +115,6 @@ module API
current_user && current_user.admin?
end
def current_user_allowed_to(permission, work_package)
current_user && current_user.allowed_to?(permission, work_package.project)
end
private
def work_package

@ -74,6 +74,10 @@ module API
"#{root}/categories/#{id}"
end
def self.create_work_package_form(project_id)
"#{work_packages_by_project(project_id)}/form"
end
def self.priorities
"#{root}/priorities"
end
@ -200,6 +204,10 @@ module API
"#{work_package(id)}/watchers"
end
def self.work_packages_by_project(project_id)
"#{project(project_id)}/work_packages"
end
def self.root_path
@@root_path ||= Class.new.tap do |c|
c.extend(::API::V3::Utilities::PathHelper)

@ -30,7 +30,7 @@
module API
module V3
module WorkPackages
class WorkPackageContract < ::API::Contracts::ModelContract
class BaseContract < ::API::Contracts::ModelContract
attribute :subject
attribute :description
attribute :start_date, :due_date
@ -39,10 +39,7 @@ module API
attribute :priority_id
attribute :category_id
attribute :fixed_version_id
attribute :lock_version do
errors.add :error_conflict, '' if model.lock_version.nil? || model.lock_version_changed?
end
attribute :lock_version
attribute :parent_id do
if model.changed.include? 'parent_id'
@ -100,27 +97,11 @@ module API
@can = WorkPackagePolicy.new(user)
end
validate :user_allowed_to_access
validate :user_allowed_to_edit
extend Reform::Form::ActiveModel::ModelValidations
copy_validations_from WorkPackage
private
# TODO: when someone every fixes the way errors are added in the contract:
# find a solution to ensure that THIS validation supersedes others (i.e. show 404 if
# there is no access allowed)
def user_allowed_to_access
unless ::WorkPackage.visible(@user).exists?(model)
errors.add :error_not_found, I18n.t('api_v3.errors.code_404')
end
end
def user_allowed_to_edit
errors.add :error_unauthorized, '' unless @can.allowed?(model, :edit)
end
def validate_people_visible(attribute, id_attribute, list)
id = model[id_attribute]

@ -1,3 +1,4 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
@ -26,25 +27,22 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/form/form_representer'
module API
module V3
module WorkPackages
module Form
class FormAPI < ::API::OpenProjectAPI
post '/form' do
write_work_package_attributes
write_request_valid?
class CreateContract < BaseContract
# These attributes need to be set during creation and cannot be modified via representer.
# Hence making them writable here is unproblematic.
attribute :project_id
attribute :author_id
validate :user_allowed_to_add
error = ::API::Errors::ErrorBase.create(@work_package.errors)
private
if error.is_a? ::API::Errors::Validation
status 200
FormRepresenter.new(@work_package, current_user: current_user)
else
fail error
end
def user_allowed_to_add
unless @user.allowed_to?(:add_work_packages, model.project)
errors.add :error_unauthorized, ''
end
end
end

@ -0,0 +1,47 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/work_packages_shared_helpers'
module API
module V3
module WorkPackages
class CreateFormAPI < ::API::OpenProjectAPI
resource :form do
helpers ::API::V3::WorkPackages::WorkPackagesSharedHelpers
post do
create_work_package_form(create_service.create,
contract_class: CreateContract,
form_class: CreateFormRepresenter)
end
end
end
end
end
end

@ -0,0 +1,67 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module WorkPackages
class CreateFormRepresenter < FormRepresenter
link :self do
{
href: api_v3_paths.create_work_package_form(represented.project_id),
method: :post
}
end
link :validate do
{
href: api_v3_paths.create_work_package_form(represented.project_id),
method: :post
}
end
link :previewMarkup do
{
href: api_v3_paths.render_markup(link: api_v3_paths.project(represented.project_id)),
method: :post
}
end
link :commit do
{
href: api_v3_paths.work_packages_by_project(represented.project_id),
method: :post
} if current_user.allowed_to?(:edit_work_packages, represented.project) &&
# Calling valid? on represented empties the list of errors
# also removing errors from other sources (like contracts).
represented.errors.empty?
end
end
end
end
end

@ -1,113 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module V3
module WorkPackages
module Form
class FormRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
include API::V3::Utilities::PathHelper
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def initialize(model, options = {})
@current_user = options[:current_user]
super(model)
end
property :_type, exec_context: :decorator, writeable: false
link :self do
{
href: api_v3_paths.work_package_form(represented.id),
}
end
link :validate do
{
href: api_v3_paths.work_package_form(represented.id),
method: :post
}
end
link :previewMarkup do
{
href: api_v3_paths.render_markup(link: api_v3_paths.work_package(represented.id)),
method: :post
}
end
link :commit do
{
href: api_v3_paths.work_package(represented.id),
method: :patch
} if @current_user.allowed_to?(:edit_work_packages, represented.project) &&
# Calling valid? on represented empties the list of errors
# also removing errors from other sources (like contracts).
represented.errors.empty?
end
property :payload,
embedded: true,
decorator: -> (represented, *) {
Form::WorkPackagePayloadRepresenter.create_class(represented)
},
getter: -> (*) { self }
property :schema,
embedded: true,
exec_context: :decorator,
getter: -> (*) {
schema = Schema::WorkPackageSchema.new(work_package: represented)
Schema::WorkPackageSchemaRepresenter.create(schema,
form_embedded: true,
current_user: @current_user)
}
property :validation_errors, embedded: true, exec_context: :decorator
def _type
'Form'
end
def validation_errors
::API::Errors::Validation.create(represented.errors.dup).inject({}) do |h, (k, v)|
h[k] = ::API::V3::Errors::ErrorRepresenter.new(v)
h
end
end
end
end
end
end
end

@ -1,119 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module V3
module WorkPackages
module Form
class WorkPackageAttributeLinksRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
include API::V3::Utilities::PathHelper
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
injector_class.create_value_representer_for_link_patching(work_package,
WorkPackageAttributeLinksRepresenter)
end
def create(work_package)
create_class(work_package).new(work_package)
end
end
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def self.linked_property(property,
namespace: property.to_s.pluralize,
association: "#{property}_id",
path: property,
show_if: true)
property property,
exec_context: :decorator,
getter: -> (*) {
get_path(get_method: association,
path: path)
},
setter: -> (value, *) {
parse_link(property: property,
namespace: namespace,
value: value,
setter_method: :"#{association}=")
},
if: show_if
end
linked_property :type
linked_property :status
linked_property :assignee,
namespace: :users,
association: :assigned_to_id,
path: :user
linked_property :responsible,
namespace: :users,
association: :responsible_id,
path: :user
linked_property :category
linked_property :version,
association: :fixed_version_id
linked_property :priority
private
def get_path(get_method: nil, path: nil)
id = represented.send(get_method)
{ href: (api_v3_paths.send(path, id) if id) }
end
def parse_link(property: nil, namespace: nil, value: {}, setter_method: nil)
return unless value.has_key?('href')
resource = parse_resource(property, namespace, value['href'])
represented.send(setter_method, resource)
end
def parse_resource(property, ns, href)
return nil unless href
::API::Utilities::ResourceLinkParser.parse_id href,
property: property,
expected_version: '3',
expected_namespace: ns
end
end
end
end
end
end

@ -1,157 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module V3
module WorkPackages
module Form
class WorkPackagePayloadRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
injector_class.create_value_representer_for_property_patching(work_package,
WorkPackagePayloadRepresenter)
end
def create(work_package, options = {})
create_class(work_package).new(work_package, options)
end
end
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def initialize(represented, options = {})
if options[:enforce_lock_version_validation]
# enforces availibility validation of lock_version
represented.lock_version = nil
end
super(represented)
end
property :linked_resources,
as: :_links,
exec_context: :decorator,
getter: -> (*) {
work_package_attribute_links_representer represented
},
setter: -> (value, *) {
representer = work_package_attribute_links_representer represented
representer.from_json(value.to_json)
}
property :lock_version
property :subject, render_nil: true
property :done_ratio,
as: :percentageDone,
render_nil: true,
if: -> (*) { Setting.work_package_done_ratio != 'disabled' }
property :estimated_hours,
as: :estimatedTime,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_duration_from_hours(represented.estimated_hours,
allow_nil: true)
},
setter: -> (value, *) {
represented.estimated_hours = datetime_formatter.parse_duration_to_hours(
value,
'estimated_hours',
allow_nil: true)
},
render_nil: true
property :description,
exec_context: :decorator,
getter: -> (*) {
API::Decorators::Formattable.new(represented.description, object: represented)
},
setter: -> (value, *) { represented.description = value['raw'] },
render_nil: true
property :parent_id,
writeable: true,
render_nil: true
property :project_id,
getter: -> (*) { nil },
render_nil: false
property :start_date,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_date(represented.start_date, allow_nil: true)
},
setter: -> (value, *) {
represented.start_date = datetime_formatter.parse_date(value,
'startDate',
allow_nil: true)
},
render_nil: true
property :due_date,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_date(represented.due_date, allow_nil: true)
},
setter: -> (value, *) {
represented.due_date = datetime_formatter.parse_date(value,
'dueDate',
allow_nil: true)
},
render_nil: true
property :version_id,
getter: -> (*) { nil },
setter: -> (value, *) { self.fixed_version_id = value },
render_nil: false
property :created_at,
getter: -> (*) { nil }, render_nil: false
property :updated_at,
getter: -> (*) { nil }, render_nil: false
private
def datetime_formatter
API::V3::Utilities::DateTimeFormatter
end
def work_package_attribute_links_representer(represented)
::API::V3::WorkPackages::Form::WorkPackageAttributeLinksRepresenter.create represented
end
end
end
end
end
end

@ -0,0 +1,64 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module WorkPackages
class FormRepresenter < ::API::Decorators::Single
property :payload,
embedded: true,
decorator: -> (represented, *) {
WorkPackagePayloadRepresenter.create_class(represented)
},
getter: -> (*) { self }
property :schema,
embedded: true,
exec_context: :decorator,
getter: -> (*) {
schema = Schema::WorkPackageSchema.new(work_package: represented)
Schema::WorkPackageSchemaRepresenter.create(schema,
form_embedded: true,
current_user: current_user)
}
property :validation_errors, embedded: true, exec_context: :decorator
def _type
'Form'
end
def validation_errors
::API::Errors::Validation.create(represented.errors.dup).inject({}) do |h, (k, v)|
h[k] = ::API::V3::Errors::ErrorRepresenter.new(v)
h
end
end
end
end
end
end

@ -94,7 +94,9 @@ module API
schema :spent_time,
type: 'Duration',
writable: false,
show_if: -> (_) { current_user_allowed_to(:view_time_entries) }
show_if: -> (_) do
current_user_allowed_to(:view_time_entries, context: represented.project)
end
schema :percentage_done,
type: 'Integer',
@ -193,10 +195,6 @@ module API
title: priority.name
}
}
def current_user_allowed_to(permission)
current_user && current_user.allowed_to?(permission, represented.project)
end
end
end
end

@ -0,0 +1,59 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module WorkPackages
class UpdateContract < BaseContract
attribute :lock_version do
errors.add :error_conflict, '' if model.lock_version.nil? || model.lock_version_changed?
end
validate :user_allowed_to_access
validate :user_allowed_to_edit
private
def user_allowed_to_edit
errors.add :error_unauthorized, '' unless @can.allowed?(model, :edit)
end
# TODO: when someone ever fixes the way errors are added in the contract:
# find a solution to ensure that THIS validation supersedes others (i.e. show 404 if
# there is no access allowed)
def user_allowed_to_access
unless ::WorkPackage.visible(@user).exists?(model) || true
errors.add :error_not_found, I18n.t('api_v3.errors.code_404')
end
end
end
end
end
end

@ -0,0 +1,47 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/work_packages_shared_helpers'
module API
module V3
module WorkPackages
class UpdateFormAPI < ::API::OpenProjectAPI
resource :form do
helpers ::API::V3::WorkPackages::WorkPackagesSharedHelpers
post do
create_work_package_form(@work_package,
contract_class: UpdateContract,
form_class: UpdateFormRepresenter)
end
end
end
end
end
end

@ -0,0 +1,67 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module WorkPackages
class UpdateFormRepresenter < FormRepresenter
link :self do
{
href: api_v3_paths.work_package_form(represented.id),
method: :post
}
end
link :validate do
{
href: api_v3_paths.work_package_form(represented.id),
method: :post
}
end
link :previewMarkup do
{
href: api_v3_paths.render_markup(link: api_v3_paths.work_package(represented.id)),
method: :post
}
end
link :commit do
{
href: api_v3_paths.work_package(represented.id),
method: :patch
} if current_user.allowed_to?(:edit_work_packages, represented.project) &&
# Calling valid? on represented empties the list of errors
# also removing errors from other sources (like contracts).
represented.errors.empty?
end
end
end
end
end

@ -0,0 +1,118 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module V3
module WorkPackages
class WorkPackageAttributeLinksRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
include API::V3::Utilities::PathHelper
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
injector_class.create_value_representer_for_link_patching(
work_package,
WorkPackageAttributeLinksRepresenter)
end
def create(work_package)
create_class(work_package).new(work_package)
end
end
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def self.linked_property(property,
namespace: property.to_s.pluralize,
association: "#{property}_id",
path: property,
show_if: true)
property property,
exec_context: :decorator,
getter: -> (*) {
get_path(get_method: association,
path: path)
},
setter: -> (value, *) {
parse_link(property: property,
namespace: namespace,
value: value,
setter_method: :"#{association}=")
},
if: show_if
end
linked_property :type
linked_property :status
linked_property :assignee,
namespace: :users,
association: :assigned_to_id,
path: :user
linked_property :responsible,
namespace: :users,
association: :responsible_id,
path: :user
linked_property :category
linked_property :version,
association: :fixed_version_id
linked_property :priority
private
def get_path(get_method: nil, path: nil)
id = represented.send(get_method)
{ href: (api_v3_paths.send(path, id) if id) }
end
def parse_link(property: nil, namespace: nil, value: {}, setter_method: nil)
return unless value.has_key?('href')
resource = parse_resource(property, namespace, value['href'])
represented.send(setter_method, resource)
end
def parse_resource(property, ns, href)
return nil unless href
::API::Utilities::ResourceLinkParser.parse_id href,
property: property,
expected_version: '3',
expected_namespace: ns
end
end
end
end
end

@ -0,0 +1,147 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module V3
module WorkPackages
class WorkPackagePayloadRepresenter < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
class << self
def create_class(work_package)
injector_class = ::API::V3::Utilities::CustomFieldInjector
injector_class.create_value_representer_for_property_patching(
work_package,
WorkPackagePayloadRepresenter)
end
def create(work_package)
create_class(work_package).new(work_package)
end
end
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def initialize(represented)
super(represented)
end
property :linked_resources,
as: :_links,
exec_context: :decorator,
getter: -> (*) {
work_package_attribute_links_representer represented
},
setter: -> (value, *) {
representer = work_package_attribute_links_representer represented
representer.from_json(value.to_json)
}
property :lock_version
property :subject, render_nil: true
property :done_ratio,
as: :percentageDone,
render_nil: true,
if: -> (*) { Setting.work_package_done_ratio != 'disabled' }
property :estimated_hours,
as: :estimatedTime,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_duration_from_hours(represented.estimated_hours,
allow_nil: true)
},
setter: -> (value, *) {
represented.estimated_hours = datetime_formatter.parse_duration_to_hours(
value,
'estimated_hours',
allow_nil: true)
},
render_nil: true
property :description,
exec_context: :decorator,
getter: -> (*) {
API::Decorators::Formattable.new(represented.description, object: represented)
},
setter: -> (value, *) { represented.description = value['raw'] },
render_nil: true
property :parent_id,
writeable: true,
render_nil: true
property :start_date,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_date(represented.start_date, allow_nil: true)
},
setter: -> (value, *) {
represented.start_date = datetime_formatter.parse_date(value,
'startDate',
allow_nil: true)
},
render_nil: true
property :due_date,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_date(represented.due_date, allow_nil: true)
},
setter: -> (value, *) {
represented.due_date = datetime_formatter.parse_date(value,
'dueDate',
allow_nil: true)
},
render_nil: true
property :version_id,
getter: -> (*) { nil },
setter: -> (value, *) { self.fixed_version_id = value },
render_nil: false
property :created_at,
getter: -> (*) { nil }, render_nil: false
property :updated_at,
getter: -> (*) { nil }, render_nil: false
private
def datetime_formatter
API::V3::Utilities::DateTimeFormatter
end
def work_package_attribute_links_representer(represented)
::API::V3::WorkPackages::WorkPackageAttributeLinksRepresenter.create represented
end
end
end
end
end

@ -53,7 +53,7 @@ module API
href: api_v3_paths.work_package_form(represented.id),
method: :post,
title: "Update #{represented.subject}"
} if current_user_allowed_to(:edit_work_packages)
} if current_user_allowed_to(:edit_work_packages, context: represented.project)
end
link :schema do
@ -67,7 +67,7 @@ module API
href: api_v3_paths.work_package(represented.id),
method: :patch,
title: "Update #{represented.subject}"
} if current_user_allowed_to(:edit_work_packages)
} if current_user_allowed_to(:edit_work_packages, context: represented.project)
end
link :delete do
@ -75,7 +75,7 @@ module API
href: work_packages_bulk_path(ids: represented),
method: :delete,
title: "Delete #{represented.subject}"
} if current_user_allowed_to(:delete_work_packages)
} if current_user_allowed_to(:delete_work_packages, context: represented.project)
end
link :log_time do
@ -83,7 +83,7 @@ module API
href: new_work_package_time_entry_path(represented),
type: 'text/html',
title: "Log time on #{represented.subject}"
} if current_user_allowed_to(:log_time)
} if current_user_allowed_to(:log_time, context: represented.project)
end
link :duplicate do
@ -91,7 +91,7 @@ module API
href: new_project_work_package_path(represented.project, copy_from: represented),
type: 'text/html',
title: "Duplicate #{represented.subject}"
} if current_user_allowed_to(:add_work_packages)
} if current_user_allowed_to(:add_work_packages, context: represented.project)
end
link :move do
@ -99,7 +99,7 @@ module API
href: new_work_package_move_path(represented),
type: 'text/html',
title: "Move #{represented.subject}"
} if current_user_allowed_to(:move_work_packages)
} if current_user_allowed_to(:move_work_packages, context: represented.project)
end
linked_property :type, embed_as: ::API::V3::Types::TypeRepresenter
@ -122,7 +122,7 @@ module API
{
href: api_v3_paths.available_watchers(represented.id),
title: 'Available Watchers'
} if current_user_allowed_to(:add_work_package_watchers)
} if current_user_allowed_to(:add_work_package_watchers, context: represented.project)
end
link :watchChanges do
@ -132,7 +132,7 @@ module API
data: { user_id: current_user.id },
title: 'Watch work package'
} if !current_user.anonymous? &&
current_user_allowed_to(:view_work_packages) &&
current_user_allowed_to(:view_work_packages, context: represented.project) &&
!represented.watcher_users.include?(current_user)
end
@ -141,7 +141,7 @@ module API
href: "#{api_v3_paths.work_package_watchers(represented.id)}/#{current_user.id}",
method: :delete,
title: 'Unwatch work package'
} if current_user_allowed_to(:view_work_packages) &&
} if current_user_allowed_to(:view_work_packages, context: represented.project) &&
represented.watcher_users.include?(current_user)
end
@ -151,7 +151,7 @@ module API
method: :post,
title: 'Add watcher',
templated: true
} if current_user_allowed_to(:add_work_package_watchers)
} if current_user_allowed_to(:add_work_package_watchers, context: represented.project)
end
link :addRelation do
@ -159,7 +159,8 @@ module API
href: api_v3_paths.work_package_relations(represented.id),
method: :post,
title: 'Add relation'
} if current_user_allowed_to(:manage_work_package_relations)
} if current_user_allowed_to(:manage_work_package_relations,
context: represented.project)
end
link :addChild do
@ -168,7 +169,7 @@ module API
work_package: { parent_id: represented }),
type: 'text/html',
title: "Add child of #{represented.subject}"
} if current_user_allowed_to(:add_work_packages)
} if current_user_allowed_to(:add_work_packages, context: represented.project)
end
link :changeParent do
@ -176,7 +177,7 @@ module API
href: api_v3_paths.work_package(represented.id),
method: :patch,
title: "Change parent of #{represented.subject}"
} if current_user_allowed_to(:manage_subtasks)
} if current_user_allowed_to(:manage_subtasks, context: represented.project)
end
link :addComment do
@ -184,7 +185,7 @@ module API
href: api_v3_paths.work_package_activities(represented.id),
method: :post,
title: 'Add comment'
} if current_user_allowed_to(:add_work_package_notes)
} if current_user_allowed_to(:add_work_package_notes, context: represented.project)
end
linked_property :parent,
@ -197,7 +198,7 @@ module API
href: work_package_time_entries_path(represented.id),
type: 'text/html',
title: 'Time entries'
} if current_user_allowed_to(:view_time_entries)
} if current_user_allowed_to(:view_time_entries, context: represented.project)
end
linked_property :category, embed_as: ::API::V3::Categories::CategoryRepresenter
@ -253,7 +254,9 @@ module API
datetime_formatter.format_duration_from_hours(represented.spent_hours)
end,
writeable: false,
if: -> (_) { current_user_allowed_to(:view_time_entries) }
if: -> (_) {
current_user_allowed_to(:view_time_entries, context: represented.project)
}
property :done_ratio,
as: :percentageDone,
render_nil: true,
@ -276,7 +279,10 @@ module API
property :watchers,
embedded: true,
exec_context: :decorator,
if: -> (*) { current_user_allowed_to(:view_work_package_watchers) }
if: -> (*) {
current_user_allowed_to(:view_work_package_watchers,
context: represented.project)
}
property :attachments,
embedded: true,
@ -331,10 +337,6 @@ module API
end
end
def current_user_allowed_to(permission)
current_user && current_user.allowed_to?(permission, represented.project)
end
def visible_children
@visible_children ||= represented.children.select(&:visible?)
end

@ -27,9 +27,7 @@
#++
require 'api/v3/activities/activity_representer'
require 'api/v3/work_packages/work_package_contract'
require 'api/v3/work_packages/work_package_representer'
require 'api/v3/work_packages/form/work_package_payload_representer'
module API
module V3
@ -40,6 +38,7 @@ module API
requires :id, desc: 'Work package id'
end
route_param :id do
helpers WorkPackagesSharedHelpers
helpers do
attr_reader :work_package
@ -47,53 +46,6 @@ module API
WorkPackageRepresenter.create(@work_package,
current_user: current_user)
end
def write_work_package_attributes
if request_body
begin
# we need to merge the JSON two times:
# In Pass 1 the representer only has custom fields for the current WP type
# After Pass 1 the correct type information is merged into the WP
# In Pass 2 the representer is created with the new type info and will be able
# to also parse custom fields successfully
merge_json_into_work_package!(request_body.to_json)
merge_json_into_work_package!(request_body.to_json)
rescue ::API::Errors::Form::InvalidResourceLink => e
fail ::API::Errors::Validation.new(e.message)
end
end
end
# merges the given JSON representation into @work_package
def merge_json_into_work_package!(json)
payload = Form::WorkPackagePayloadRepresenter.create(
@work_package,
enforce_lock_version_validation: true)
payload.from_json(json)
end
def request_body
env['api.request.body']
end
def write_request_valid?
contract = WorkPackageContract.new(@work_package, current_user)
contract_valid = contract.validate
represented_valid = @work_package.valid?
return true if contract_valid && represented_valid
# We need to merge the contract errors with the model errors in
# order to have them available at one place.
contract.errors.keys.each do |key|
contract.errors[key].each do |message|
@work_package.errors.add(key, message)
end
end
false
end
end
before do
@ -109,15 +61,15 @@ module API
end
patch do
write_work_package_attributes
write_work_package_attributes(@work_package, reset_lock_version: true)
send_notifications = !(params.has_key?(:notify) && params[:notify] == 'false')
update_service = UpdateWorkPackageService.new(current_user,
@work_package,
nil,
send_notifications)
update_service = UpdateWorkPackageService.new(
user: current_user,
work_package: @work_package,
send_notifications: send_notifications)
if write_request_valid? && update_service.save
if write_request_valid?(@work_package, UpdateContract) && update_service.save
@work_package.reload
work_package_representer
@ -155,8 +107,8 @@ module API
mount ::API::V3::WorkPackages::WatchersAPI
mount ::API::V3::Relations::RelationsAPI
mount ::API::V3::WorkPackages::Form::FormAPI
mount ::API::V3::Attachments::AttachmentsByWorkPackageAPI
mount ::API::V3::WorkPackages::UpdateFormAPI
end
mount ::API::V3::WorkPackages::Schema::WorkPackageSchemasAPI

@ -0,0 +1,69 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/work_package_representer'
require 'api/v3/work_packages/work_packages_shared_helpers'
module API
module V3
module WorkPackages
class WorkPackagesByProjectAPI < ::API::OpenProjectAPI
resources :work_packages do
helpers ::API::V3::WorkPackages::WorkPackagesSharedHelpers
helpers do
def create_service
@create_service ||=
CreateWorkPackageService.new(
user: current_user,
project: @project,
send_notifications: !(params.has_key?(:notify) && params[:notify] == 'false'))
end
end
post do
work_package = create_service.create
write_work_package_attributes work_package
if write_request_valid?(work_package, WorkPackages::CreateContract) &&
create_service.save(work_package)
work_package.reload
WorkPackages::WorkPackageRepresenter.create(work_package,
current_user: current_user)
else
fail ::API::Errors::ErrorBase.create(work_package.errors.dup)
end
end
mount ::API::V3::WorkPackages::CreateFormAPI
end
end
end
end
end

@ -0,0 +1,99 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/base_contract'
require 'api/v3/work_packages/work_package_payload_representer'
module API
module V3
module WorkPackages
module WorkPackagesSharedHelpers
extend Grape::API::Helpers
def request_body
env['api.request.body']
end
# merges the given JSON representation into @work_package
def merge_json_into_work_package!(work_package, json)
payload = ::API::V3::WorkPackages::WorkPackagePayloadRepresenter.create(work_package)
payload.from_json(json)
end
def write_work_package_attributes(work_package, reset_lock_version: false)
if request_body
begin
work_package.lock_version = nil if reset_lock_version
# we need to merge the JSON two times:
# In Pass 1 the representer only has custom fields for the current WP type
# After Pass 1 the correct type information is merged into the WP
# In Pass 2 the representer is created with the new type info and will be able
# to also parse custom fields successfully
merge_json_into_work_package!(work_package, request_body.to_json)
merge_json_into_work_package!(work_package, request_body.to_json)
rescue ::API::Errors::Form::InvalidResourceLink => e
fail ::API::Errors::Validation.new(e.message)
end
end
end
def write_request_valid?(work_package, contract_class)
contract = contract_class.new(work_package, current_user)
contract_valid = contract.validate
represented_valid = work_package.valid?
return true if contract_valid && represented_valid
# We need to merge the contract errors with the model errors in
# order to have them available at one place.
contract.errors.keys.each do |key|
contract.errors[key].each do |message|
work_package.errors.add(key, message)
end
end
false
end
def create_work_package_form(work_package, contract_class:, form_class:)
write_work_package_attributes(work_package, reset_lock_version: true)
write_request_valid?(work_package, contract_class)
error = ::API::Errors::ErrorBase.create(work_package.errors)
if error.is_a? ::API::Errors::Validation
status 200
form_class.new(work_package, current_user: current_user)
else
fail error
end
end
end
end
end
end

@ -0,0 +1,73 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
describe CreateWorkPackageService do
let(:user) { FactoryGirl.build(:user) }
let(:work_package) { FactoryGirl.build(:work_package) }
let(:project) { FactoryGirl.build(:project_with_types) }
before do
allow(project).to receive(:add_work_package).and_return(work_package)
end
subject(:service) { CreateWorkPackageService.new(user: user, project: project) }
describe 'should use meaningful defaults for creation' do
it 'should use the project' do
expect(project).to receive(:add_work_package).with(hash_including(project: project))
end
it 'should use the user' do
expect(project).to receive(:add_work_package).with(hash_including(author: user))
end
it 'should use a type' do
expect(project).to receive(:add_work_package).with(hash_including(:type))
end
it 'should have a non-empty type' do
expect(project).to receive(:add_work_package).with(hash_excluding(type: nil))
end
after do
service.create
end
end
it 'should create an unsaved work_package' do
expect(service.create.new_record?).to be_truthy
end
it 'should #save records' do
wp = service.create
service.save(wp)
expect(WorkPackage.exists?(wp.id)).to be_truthy
end
end

@ -0,0 +1,61 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
describe ::API::Decorators::Single do
let(:user) { FactoryGirl.build(:user, member_in_project: project, member_through_role: role) }
let(:project) { FactoryGirl.create(:project_with_types) }
let(:role) { FactoryGirl.create(:role, permissions: permissions) }
let(:permissions) { [:view_work_packages] }
let(:model) { Object.new }
context 'no user given' do
let(:single) { ::API::Decorators::Single.new(model, context: nil) }
it 'should not authorize an empty user' do
expect(single.current_user_allowed_to(:view_work_packages, context: project)).to be_falsey
end
end
context 'user given' do
let(:single) { ::API::Decorators::Single.new(model, current_user: user) }
it 'should authorize for a given permission' do
expect(single.current_user_allowed_to(:view_work_packages, context: project)).to be_truthy
end
context 'unauthorized user' do
let(:permissions) { [] }
it 'should not authorize unauthorized user' do
expect(single.current_user_allowed_to(:view_work_packages, context: project)).to be_falsey
end
end
end
end

@ -0,0 +1,88 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
describe ::API::Contracts::ModelContract do
let(:work_package) { FactoryGirl.build(:work_package) }
let(:child_contract) { ChildContract.new(work_package) }
let(:grand_child_contract) { GrandChildContract.new(work_package) }
before do
child_contract.child_value = 0
grand_child_contract.child_value = 0
end
describe 'child' do
class ChildContract < ::API::Contracts::ModelContract
attr_accessor :child_value
attribute :child_attribute
attribute :overwritten_attribute do
@child_value = 1
end
end
it 'should collect its own writable attributes' do
expect(child_contract.writable_attributes).to include('child_attribute',
'overwritten_attribute')
end
it 'should collect its own attribute validations' do
child_contract.validate
expect(child_contract.child_value).to eq(1)
end
end
describe 'grand_child' do
class GrandChildContract < ChildContract
attr_accessor :grand_child_value
attribute :grand_child_attribute
attribute :overwritten_attribute do
@grand_child_value = 2
end
end
it 'should consider its ancestor writable attributes' do
expect(grand_child_contract.writable_attributes).to include('child_attribute',
'overwritten_attribute',
'grand_child_attribute')
end
it 'should not contain the same attribute twice' do
expect(grand_child_contract.writable_attributes.count).to eq(3)
end
it 'should execute all the validations' do
grand_child_contract.validate
expect(grand_child_contract.child_value).to eq(1)
expect(grand_child_contract.grand_child_value).to eq(2)
end
end
end

@ -29,8 +29,15 @@
require 'spec_helper'
describe ::API::V3::Projects::ProjectRepresenter do
let(:project) { FactoryGirl.build(:project) }
let(:representer) { described_class.new(project) }
include ::API::V3::Utilities::PathHelper
let(:project) { FactoryGirl.create(:project) }
let(:representer) { described_class.new(project, current_user: user) }
let(:user) do
FactoryGirl.build(:user, member_in_project: project, member_through_role: role)
end
let(:role) { FactoryGirl.create(:role, permissions: permissions) }
let(:permissions) { [:add_work_packages] }
context 'generation' do
subject(:generated) { representer.to_json }
@ -65,14 +72,40 @@ describe ::API::V3::Projects::ProjectRepresenter do
expect(subject).to have_json_path('_links/self/title')
end
describe 'create work packages' do
context 'user allowed to create work packages' do
it 'has the correct path for a create form' do
is_expected.to be_json_eql(api_v3_paths.create_work_package_form(project.id).to_json)
.at_path('_links/createWorkPackage/href')
end
it 'has the correct path to create a work package' do
is_expected.to be_json_eql(api_v3_paths.work_packages_by_project(project.id).to_json)
.at_path('_links/createWorkPackageImmediate/href')
end
end
context 'user not allowed to create work packages' do
let(:permissions) { [] }
it { is_expected.to_not have_json_path('_links/createWorkPackage/href') }
it { is_expected.to_not have_json_path('_links/createWorkPackageImmediate/href') }
end
end
describe 'categories' do
it { is_expected.to have_json_path('_links/categories') }
it { is_expected.to have_json_path('_links/categories/href') }
it 'has the correct link to its categories' do
is_expected.to be_json_eql(api_v3_paths.categories(project.id).to_json)
.at_path('_links/categories/href')
end
end
describe 'versions' do
it { is_expected.to have_json_path('_links/versions') }
it { is_expected.to have_json_path('_links/versions/href') }
it 'has the correct link to its versions' do
is_expected.to be_json_eql(api_v3_paths.versions_by_project(project.id).to_json)
.at_path('_links/versions/href')
end
end
end
end

@ -111,6 +111,14 @@ describe ::API::V3::Utilities::PathHelper do
it { is_expected.to match(/^\/api\/v3\/categories\/42/) }
end
describe '#create_work_package_form' do
subject { helper.create_work_package_form 42 }
it_behaves_like 'api v3 path'
it { is_expected.to eql('/api/v3/projects/42/work_packages/form') }
end
describe '#render_markup' do
subject { helper.render_markup(format: 'super_fancy', link: 'link-ish') }
@ -332,6 +340,14 @@ describe ::API::V3::Utilities::PathHelper do
it { is_expected.to match(/^\/api\/v3\/versions\/42\/projects/) }
end
describe '#work_packages_by_project' do
subject { helper.work_packages_by_project 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/42\/work_packages/) }
end
describe 'work packages paths' do
shared_examples_for 'api v3 work packages path' do
it { is_expected.to match(/^\/api\/v3\/work_packages/) }

@ -28,7 +28,7 @@
require 'spec_helper'
describe ::API::V3::WorkPackages::WorkPackageContract do
describe ::API::V3::WorkPackages::BaseContract do
let(:work_package) do
FactoryGirl.create(:work_package,
done_ratio: 50,

@ -0,0 +1,119 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++require 'rspec'
require 'spec_helper'
describe ::API::V3::WorkPackages::CreateFormRepresenter do
include API::V3::Utilities::PathHelper
let(:work_package) {
FactoryGirl.build(:work_package,
id: 42,
created_at: DateTime.now,
updated_at: DateTime.now)
}
let(:current_user) {
FactoryGirl.build(:user, member_in_project: work_package.project)
}
let(:representer) { described_class.new(work_package, current_user: current_user) }
context 'generation' do
subject(:generated) { representer.to_json }
describe '_links' do
it do
is_expected.to be_json_eql(
api_v3_paths.create_work_package_form(work_package.project_id).to_json)
.at_path('_links/self/href')
end
it { is_expected.to be_json_eql(:post.to_json).at_path('_links/self/method') }
describe 'validate' do
it do
is_expected.to be_json_eql(
api_v3_paths.create_work_package_form(work_package.project_id).to_json)
.at_path('_links/validate/href')
end
it { is_expected.to be_json_eql(:post.to_json).at_path('_links/validate/method') }
end
describe 'preview markup' do
it do
is_expected.to be_json_eql(
api_v3_paths.render_markup(
link: api_v3_paths.project(work_package.project_id)).to_json)
.at_path('_links/previewMarkup/href')
end
it { is_expected.to be_json_eql(:post.to_json).at_path('_links/previewMarkup/method') }
it 'contains link to work package' do
expected_preview_link =
api_v3_paths.render_markup(format: 'textile',
link: "/api/v3/projects/#{work_package.project_id}")
expect(subject).to be_json_eql(expected_preview_link.to_json)
.at_path('_links/previewMarkup/href')
end
end
describe 'commit' do
context 'valid work package' do
it do
is_expected.to be_json_eql(
api_v3_paths.work_packages_by_project(work_package.project_id).to_json)
.at_path('_links/commit/href')
end
it { is_expected.to be_json_eql(:post.to_json).at_path('_links/commit/method') }
end
context 'invalid work package' do
before { allow(work_package.errors).to receive(:empty?).and_return(false) }
it { is_expected.not_to have_json_path('_links/commit/href') }
end
context 'user with insufficient permissions' do
let(:role) { FactoryGirl.create(:role, permissions: []) }
let(:current_user) {
FactoryGirl.build(:user,
member_in_project: work_package.project,
member_through_role: role)
}
before { allow(work_package.errors).to receive(:empty?).and_return(true) }
it { is_expected.not_to have_json_path('_links/commit/href') }
end
end
end
end
end

@ -0,0 +1,82 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::WorkPackages::FormRepresenter do
include API::V3::Utilities::PathHelper
let(:work_package) {
FactoryGirl.build(:work_package,
id: 42,
created_at: DateTime.now,
updated_at: DateTime.now)
}
let(:current_user) {
FactoryGirl.build(:user, member_in_project: work_package.project)
}
let(:representer) { described_class.new(work_package, current_user: current_user) }
context 'generation' do
subject(:generated) { representer.to_json }
it { is_expected.to be_json_eql('Form'.to_json).at_path('_type') }
describe 'validation errors' do
context 'w/o errors' do
it { is_expected.to be_json_eql({}.to_json).at_path('_embedded/validationErrors') }
end
context 'with errors' do
let(:subject_error_message) { 'Subject can\'t be blank!' }
let(:status_error_message) { 'Status can\'t be blank!' }
let(:errors) {
{ subject: [subject_error_message], status: [status_error_message] }
}
let(:subject_error) { ::API::Errors::Validation.new(subject_error_message) }
let(:status_error) { ::API::Errors::Validation.new(status_error_message) }
let(:api_subject_error) { ::API::V3::Errors::ErrorRepresenter.new(subject_error) }
let(:api_status_error) { ::API::V3::Errors::ErrorRepresenter.new(status_error) }
let(:api_errors) { { subject: api_subject_error, status: api_status_error } }
before do
allow(work_package).to receive(:errors).and_return(errors)
allow(work_package.errors).to(
receive(:full_message).with(:subject, anything).and_return(subject_error_message))
allow(work_package.errors).to(
receive(:full_message).with(:status, anything).and_return(status_error_message))
end
it { is_expected.to be_json_eql(api_errors.to_json).at_path('_embedded/validationErrors') }
end
end
it { is_expected.to have_json_path('_embedded/payload') }
it { is_expected.to have_json_path('_embedded/schema') }
end
end

@ -0,0 +1,68 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
describe ::API::V3::WorkPackages::UpdateContract do
let(:work_package) { FactoryGirl.create(:work_package) }
let(:member) { FactoryGirl.build(:user) }
subject(:contract) { described_class.new(work_package, member) }
before do
allow(member).to receive(:allowed_to?).and_return(true)
end
describe 'lock_version' do
context 'no lock_version present' do
before do
work_package.lock_version = nil
contract.validate
end
it { expect(contract.errors).to include(:error_conflict) }
end
context 'lock_version changed' do
before do
work_package.lock_version += 1
contract.validate
end
it { expect(contract.errors).to include(:error_conflict) }
end
context 'lock_version present and unchanged' do
before do
contract.validate
end
it { expect(contract.errors).not_to include(:error_conflict) }
end
end
end

@ -1,3 +1,4 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
@ -24,11 +25,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::WorkPackages::Form::FormRepresenter do
describe ::API::V3::WorkPackages::UpdateFormRepresenter do
include API::V3::Utilities::PathHelper
let(:work_package) {
@ -45,8 +45,6 @@ describe ::API::V3::WorkPackages::Form::FormRepresenter do
context 'generation' do
subject(:generated) { representer.to_json }
it { is_expected.to be_json_eql('Form'.to_json).at_path('_type') }
describe '_links' do
it { is_expected.to have_json_path('_links') }
@ -100,34 +98,5 @@ describe ::API::V3::WorkPackages::Form::FormRepresenter do
end
end
end
describe 'validation errors' do
context 'w/o errors' do
it { is_expected.to be_json_eql({}.to_json).at_path('_embedded/validationErrors') }
end
context 'with errors' do
let(:subject_error_message) { 'Subject can\'t be blank!' }
let(:status_error_message) { 'Status can\'t be blank!' }
let(:errors) {
{ subject: [subject_error_message], status: [status_error_message] }
}
let(:subject_error) { ::API::Errors::Validation.new(subject_error_message) }
let(:status_error) { ::API::Errors::Validation.new(status_error_message) }
let(:api_subject_error) { ::API::V3::Errors::ErrorRepresenter.new(subject_error) }
let(:api_status_error) { ::API::V3::Errors::ErrorRepresenter.new(status_error) }
let(:api_errors) { { subject: api_subject_error, status: api_status_error } }
before do
allow(work_package).to receive(:errors).and_return(errors)
allow(work_package.errors).to receive(:full_message).with(:subject, anything)
.and_return(subject_error_message)
allow(work_package.errors).to receive(:full_message).with(:status, anything)
.and_return(status_error_message)
end
it { is_expected.to be_json_eql(api_errors.to_json).at_path('_embedded/validationErrors') }
end
end
end
end

@ -28,7 +28,7 @@
require 'spec_helper'
describe ::API::V3::WorkPackages::Form::WorkPackagePayloadRepresenter do
describe ::API::V3::WorkPackages::WorkPackagePayloadRepresenter do
let(:work_package) do
FactoryGirl.build(:work_package,
start_date: Date.today.to_datetime,

@ -0,0 +1,103 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
describe ::API::V3::WorkPackages::WorkPackagesSharedHelpers do
let(:work_package) { FactoryGirl.create(:work_package) }
let(:user) { FactoryGirl.build(:admin) }
let(:env) { { 'api.request.body' => { subject: 'foo' } } }
let(:helper_class) {
Class.new do
include ::API::V3::WorkPackages::WorkPackagesSharedHelpers
def initialize(user, env)
@user = user
@env = env
end
def env
@env
end
def current_user
@user
end
def status(_code)
end
end
}
let(:helper) { helper_class.new(user, env) }
describe '#create_work_package_form' do
subject do
helper.create_work_package_form(work_package,
contract_class: ::API::V3::WorkPackages::CreateContract,
form_class: ::API::V3::WorkPackages::CreateFormRepresenter)
end
context 'valid parameters' do
it 'should return a form' do
expect(subject).to be_a(::API::V3::WorkPackages::CreateFormRepresenter)
end
end
context 'invalid parameters' do
context 'validation errors' do
let(:env) { { 'api.request.body' => { subject: '' } } }
it 'does not raise for validation errors' do
expect(subject.validation_errors.any?).to be_truthy
end
end
context 'other errors' do
let(:env) { { 'api.request.body' => { percentageDone: 1 } } }
subject do
lambda do
helper.create_work_package_form(
work_package,
contract_class: ::API::V3::WorkPackages::CreateContract,
form_class: ::API::V3::WorkPackages::CreateFormRepresenter)
end
end
before do
allow(Setting).to receive(:work_package_done_ratio).and_return('status')
end
it 'should return all other errors' do
expect(subject).to raise_error(API::Errors::UnwritableProperty)
end
end
end
end
end

@ -67,28 +67,38 @@ describe 'API v3 Render resource' do
'Hello World! This *is* textile with a ' +
'"link":http://community.openproject.org and ümläutß.'
end
let(:text) do
'<p>Hello World! This <strong>is</strong> textile with a ' +
it_behaves_like 'valid response' do
let(:text) do
'<p>Hello World! This <strong>is</strong> textile with a ' +
'<a href="http://community.openproject.org" class="external">link</a> ' +
'and ümläutß.</p>'
end
end
it_behaves_like 'valid response'
end
context 'with context' do
let(:context) { api_v3_paths.work_package work_package.id }
let(:params) { "Hello World! Have a look at ##{work_package.id}" }
let(:id) { work_package.id }
let(:href) { "/work_packages/#{id}" }
let(:title) { "#{work_package.subject} (#{work_package.status})" }
let(:text) {
"<p>Hello World! Have a look at <a href=\"#{href}\" "\
"class=\"issue work_package status-1 priority-1\" "\
"title=\"#{title}\">##{id}</a></p>"
"class=\"issue work_package status-1 priority-1\" "\
"title=\"#{title}\">##{id}</a></p>"
}
it_behaves_like 'valid response'
context 'with work package context' do
let(:context) { api_v3_paths.work_package work_package.id }
it_behaves_like 'valid response'
end
context 'with project context' do
let(:context) { "/api/v3/projects/#{work_package.project_id}" }
it_behaves_like 'valid response'
end
end
end
@ -158,9 +168,10 @@ describe 'API v3 Render resource' do
describe 'response' do
describe 'valid' do
let(:params) { "Hello *World*! Have a look at #1\n\nwith two lines." }
let(:text) { "<p>Hello *World*! Have a look at #1</p>\n\n<p>with two lines.</p>" }
it_behaves_like 'valid response'
it_behaves_like 'valid response' do
let(:text) { "<p>Hello *World*! Have a look at #1</p>\n\n<p>with two lines.</p>" }
end
end
end
end

@ -803,23 +803,6 @@ h4. things we like
it_behaves_like 'read-only violation', 'updatedAt'
end
end
context 'project id' do
let(:another_project) { FactoryGirl.create(:project) }
let!(:another_membership) {
FactoryGirl.create(:member,
user: current_user,
project: another_project,
roles: [role])
}
let(:params) { valid_params.merge(projectId: another_project.id) }
include_context 'patch request'
it { expect(response.status).to eq(422) }
it_behaves_like 'read-only violation', 'projectId'
end
end
context 'multiple read-only attributes' do

@ -0,0 +1,54 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
require 'rack/test'
describe ::API::V3::WorkPackages::CreateFormAPI do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
let(:project) { FactoryGirl.create(:project, id: 5) }
let(:post_path) { api_v3_paths.create_work_package_form(project.id) }
let(:user) { FactoryGirl.build(:admin) }
before do
allow(User).to receive(:current).and_return(user)
post post_path
end
subject(:response) { last_response }
it 'should return 200(OK)' do
expect(response.status).to eq(200)
end
it 'should be of type form' do
expect(response.body).to be_json_eql('Form'.to_json).at_path('_type')
end
end

@ -0,0 +1,148 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 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-2013 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 doc/COPYRIGHT.rdoc for more details.
require 'spec_helper'
require 'rack/test'
describe API::V3::WorkPackages::WorkPackagesByProjectAPI, type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
let(:project) { FactoryGirl.create(:project_with_types, is_public: false) }
describe '#post' do
let(:status) { FactoryGirl.build(:status, is_default: true) }
let(:priority) { FactoryGirl.build(:priority, is_default: true) }
let(:parameters) { { subject: 'new work packages' } }
let(:permissions) { [:add_work_packages, :view_project] }
let(:role) { FactoryGirl.create(:role, permissions: permissions) }
let(:current_user) do
FactoryGirl.build(:user, member_in_project: project, member_through_role: role)
end
let(:path) { api_v3_paths.work_packages_by_project project.id }
before do
status.save!
priority.save!
allow(User).to receive(:current).and_return current_user
ActionMailer::Base.deliveries.clear
post path, parameters.to_json, 'CONTENT_TYPE' => 'application/json'
end
context 'notifications' do
let(:permissions) { [:add_work_packages, :view_project, :view_work_packages] }
it 'sends a mail by default' do
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
context 'without notifications' do
let(:path) { "#{api_v3_paths.work_packages_by_project(project.id)}?notify=false" }
it 'should not send a mail' do
expect(ActionMailer::Base.deliveries.count).to eq(0)
end
end
context 'with notifications' do
let(:path) { "#{api_v3_paths.work_packages_by_project(project.id)}?notify=true" }
it 'should send a mail' do
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
end
end
it 'should return Created(201)' do
expect(last_response.status).to eq(201)
end
it 'should create a work package' do
expect(WorkPackage.all.count).to eq(1)
end
it 'should use the given parameters' do
expect(WorkPackage.first.subject).to eq(parameters[:subject])
end
context 'no permissions' do
let(:current_user) { FactoryGirl.build(:user) }
it 'should hide the endpoint' do
expect(last_response.status).to eq(404)
end
end
context 'view_project permission' do
# Note that this just removes the add_work_packages permission
# view_project is actually provided by being a member of the project
let(:permissions) { [:view_project] }
it 'should point out the missing permission' do
expect(last_response.status).to eq(403)
end
end
context 'empty parameters' do
let(:parameters) { {} }
it_behaves_like 'constraint violation' do
let(:message) { "Subject can't be blank" }
end
it 'should not create a work package' do
expect(WorkPackage.all.count).to eq(0)
end
end
context 'bogus parameters' do
let(:parameters) { { bogus: nil } }
it_behaves_like 'constraint violation' do
let(:message) { "Subject can't be blank" }
end
it 'should not create a work package' do
expect(WorkPackage.all.count).to eq(0)
end
end
context 'invalid value' do
let(:parameters) { { subject: nil } }
it_behaves_like 'constraint violation' do
let(:message) { "Subject can't be blank" }
end
it 'should not create a work package' do
expect(WorkPackage.all.count).to eq(0)
end
end
end
end
Loading…
Cancel
Save