add schema to project api and complete project resource

pull/7505/head
ulferts 5 years ago
parent 0540001031
commit cc41884048
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 52
      app/contracts/projects/base_contract.rb
  2. 32
      app/contracts/projects/create_contract.rb
  3. 32
      app/contracts/projects/update_contract.rb
  4. 21
      app/models/project.rb
  5. 2
      app/services/api/v3/work_package_collection_from_query_service.rb
  6. 148
      docs/api/apiv3/endpoints/projects.apib
  7. 6
      lib/api/decorators/aggregation_group.rb
  8. 4
      lib/api/utilities/payload_representer.rb
  9. 9
      lib/api/v3/projects/project_representer.rb
  10. 3
      lib/api/v3/projects/projects_api.rb
  11. 41
      lib/api/v3/projects/schemas/project_schema_api.rb
  12. 83
      lib/api/v3/projects/schemas/project_schema_representer.rb
  13. 1
      lib/api/v3/schemas/schema_collection_representer.rb
  14. 12
      lib/api/v3/utilities/custom_field_injector.rb
  15. 6
      lib/api/v3/utilities/endpoints/schema.rb
  16. 1
      lib/api/v3/utilities/path_helper.rb
  17. 2
      lib/api/v3/work_packages/form_representer.rb
  18. 2
      lib/api/v3/work_packages/parse_params_service.rb
  19. 4
      lib/api/v3/work_packages/schema/work_package_schemas_api.rb
  20. 2
      lib/api/v3/work_packages/work_package_collection_representer.rb
  21. 4
      lib/api/v3/work_packages/work_package_sums_representer.rb
  22. 4
      modules/backlogs/spec/api/work_packages/schema/work_package_sums_schema_representer_spec.rb
  23. 3
      modules/backlogs/spec/api/work_packages/work_package_sums_representer_spec.rb
  24. 3
      modules/costs/spec/lib/api/v3/work_packages/work_package_sums_representer_spec.rb
  25. 45
      spec/contracts/projects/create_contract_spec.rb
  26. 171
      spec/contracts/projects/shared_contract_examples.rb
  27. 47
      spec/contracts/projects/update_contract_spec.rb
  28. 8
      spec/controllers/projects_controller_spec.rb
  29. 3
      spec/lib/api/decorators/aggregation_group_spec.rb
  30. 76
      spec/lib/api/v3/projects/project_representer_spec.rb
  31. 233
      spec/lib/api/v3/projects/schemas/project_schema_representer_spec.rb
  32. 1
      spec/lib/api/v3/utilities/path_helper_spec.rb
  33. 2
      spec/lib/api/v3/work_packages/schema/work_package_sums_schema_representer_spec.rb
  34. 3
      spec/lib/api/v3/work_packages/work_package_sums_representer_spec.rb
  35. 37
      spec/models/project_spec.rb
  36. 68
      spec/requests/api/v3/projects/schemas/project_schema_resource_spec.rb
  37. 9
      spec/services/api/v3/work_package_collection_from_query_service_spec.rb
  38. 12
      spec_legacy/unit/project_spec.rb
  39. 4
      spec_legacy/unit/user_spec.rb

@ -28,12 +28,56 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'model_contract'
module Projects
class BaseContract < ::ModelContract
def self.model
::Project
include AssignableValuesContract
attribute :name
attribute :identifier
attribute :description
attribute :is_public
attribute :status do
validate_status_not_nil
validate_status_included
end
attribute :parent do
validate_parent_visible
end
attribute_alias :is_public, :public
def validate_status_not_nil
errors.add(:status, :blank) if model.status.nil?
end
def validate_status_included
if model.status.present? && !assignable_statuses.include?(model.status)
errors.add(:status, :inclusion)
end
end
def validate_parent_visible
errors.add(:parent, :does_not_exist) if model.parent && model.parent_id_changed? && !model.parent.visible?
end
def assignable_parents
Project.visible
end
def assignable_statuses
Project.statuses.keys
end
def assignable_custom_field_values(custom_field)
custom_field.possible_values
end
def available_custom_fields
if user.admin?
model.available_custom_fields
else
model.available_custom_fields.select(&:visible?)
end
end
end
end

@ -0,0 +1,32 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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 doc/COPYRIGHT.rdoc for more details.
#++
module Projects
class CreateContract < BaseContract
end
end

@ -0,0 +1,32 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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 doc/COPYRIGHT.rdoc for more details.
#++
module Projects
class UpdateContract < BaseContract
end
end

@ -39,11 +39,13 @@ class Project < ActiveRecord::Base
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 9
enum status: { active: STATUS_ACTIVE, archived: STATUS_ARCHIVED }
# Maximum length for project identifiers
IDENTIFIER_MAX_LENGTH = 100
# reserved identifiers
RESERVED_IDENTIFIERS = %w(new)
RESERVED_IDENTIFIERS = %w(new).freeze
# Specific overridden Activities
has_many :time_entry_activities
@ -163,7 +165,7 @@ class Project < ActiveRecord::Base
# starts with lower-case letter, a-z, 0-9, dashes and underscores afterwards
validates :identifier,
format: { with: /\A[a-z][a-z0-9\-_]*\z/ },
if: -> (p) { p.identifier_changed? }
if: ->(p) { p.identifier_changed? }
# reserved words
validates_exclusion_of :identifier, in: RESERVED_IDENTIFIERS
@ -175,13 +177,12 @@ class Project < ActiveRecord::Base
scope :has_module, ->(mod) {
where(["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s])
}
scope :active, -> { where(status: STATUS_ACTIVE) }
scope :public_projects, -> { where(is_public: true) }
scope :visible, ->(user = User.current) { Project.visible_by(user) }
scope :newest, -> { order(created_on: :desc) }
def visible?(user = User.current)
self.active? and (self.is_public? or user.admin? or user.member_of?(self))
active? and (is_public? or user.admin? or user.member_of?(self))
end
def copy_allowed?
@ -311,14 +312,6 @@ class Project < ActiveRecord::Base
cond
end
def active?
status == STATUS_ACTIVE
end
def archived?
status == STATUS_ARCHIVED
end
# Archives the project and its descendants
def archive
# Check that there is no issue of a non descendant project that is assigned
@ -331,9 +324,11 @@ class Project < ActiveRecord::Base
.first
return false
end
Project.transaction do
archive!
end
true
end
@ -341,6 +336,7 @@ class Project < ActiveRecord::Base
# All its ancestors must be active
def unarchive
return false if ancestors.detect { |a| !a.active? }
update_attribute :status, STATUS_ACTIVE
end
@ -348,6 +344,7 @@ class Project < ActiveRecord::Base
# by the current user
def allowed_parents
return @allowed_parents if @allowed_parents
@allowed_parents = Project.allowed_to(User.current, :add_subprojects)
@allowed_parents = @allowed_parents - self_and_descendants
if User.current.allowed_to?(:add_project, nil, global: true) || (!new_record? && parent.nil?)

@ -96,7 +96,7 @@ module API
format_query_sums results.all_sums_for_group(group)
end
::API::Decorators::AggregationGroup.new(group, count, query: results.query, sums: sums)
::API::Decorators::AggregationGroup.new(group, count, query: results.query, sums: sums, current_user: current_user)
end
end

@ -22,14 +22,16 @@ Depending on custom fields defined for projects, additional links might exist.
## Local Properties
| Property | Description | Type | Constraints | Supported operations |
| :---------: | ------------- | ---- | ----------- | -------------------- |
| id | Projects's id | Integer | x > 0 | READ |
| identifier | | String | | READ |
| name | | String | | READ |
| description | | Formattable | | READ |
| createdAt | Time of creation | DateTime | | READ |
| updatedAt | Time of the most recent change to the project | DateTime | | READ |
| Property | Description | Type | Constraints | Supported operations |
| :---------: | ------------- | ---- | ----------- | -------------------- |
| id | Projects's id | Integer | x > 0 | READ |
| identifier | | String | | READ |
| name | | String | | READ |
| status | The project's status | String | 'active' or 'archived' | READ |
| public | Indicates whether the project is accessible for everybody | Boolean | | READ |
| description | | Formattable | | READ |
| createdAt | Time of creation | DateTime | | READ |
| updatedAt | Time of the most recent change to the project | DateTime | | READ |
Depending on custom fields defined for projects, additional properties might exist.
@ -76,6 +78,8 @@ Depending on custom fields defined for projects, additional properties might exi
"id": 1,
"identifier": "project_identifier",
"name": "Project example",
"status": active,
"public": false,
"description": {
"format": "markdown",
"raw": "Lorem **ipsum** dolor sit amet",
@ -154,6 +158,8 @@ Depending on custom fields defined for projects, additional properties might exi
"id": 6,
"identifier": "a_project",
"name": "A project",
"status": "active",
"public": false,
"description": {
"format": "markdown",
"raw": "Lorem **ipsum** dolor sit amet",
@ -188,6 +194,8 @@ Depending on custom fields defined for projects, additional properties might exi
"id": 14,
"identifier": "another_project",
"name": "Another project",
"status": "archived",
"public": true,
"description": {
"format": "markdown",
"raw": "",
@ -215,6 +223,130 @@ Returns a collection of projects. The collection can be filtered via query param
[Projects][]
## Projects schema [/api/v3/projects/schemas]
+ Model
+ Body
{
"_type": "Schema",
"_dependencies": [],
"id": {
"type": "Integer",
"name": "ID",
"required": true,
"hasDefault": false,
"writable": false
},
"name": {
"type": "String",
"name": "Name",
"required": true,
"hasDefault": false,
"writable": true,
"minLength": 1,
"maxLength": 255
},
"identifier": {
"type": "String",
"name": "Identifier",
"required": true,
"hasDefault": false,
"writable": true,
"minLength": 1,
"maxLength": 100
},
"description": {
"type": "Formattable",
"name": "Description",
"required": false,
"hasDefault": false,
"writable": true
},
"public": {
"type": "Boolean",
"name": "Public",
"required": true,
"hasDefault": false,
"writable": true
},
"status": {
"type": "String",
"name": "Status",
"required": true,
"hasDefault": false,
"writable": true,
"visibility": "default",
"_links": {}
},
"createdAt": {
"type": "DateTime",
"name": "Created on",
"required": true,
"hasDefault": false,
"writable": false
},
"updatedAt": {
"type": "DateTime",
"name": "Updated on",
"required": true,
"hasDefault": false,
"writable": false
},
"customField30": {
"type": "Integer",
"name": "Integer project custom field",
"required": false,
"hasDefault": false,
"writable": true,
"visibility": "default"
},
"customField31": {
"type": "CustomOption",
"name": "List project custom field",
"required": false,
"hasDefault": false,
"writable": true,
"visibility": "default",
"_links": {}
},
"customField32": {
"type": "Version",
"name": "Version project custom field",
"required": false,
"hasDefault": false,
"writable": true,
"visibility": "default",
"_links": {}
},
"customField34": {
"type": "Boolean",
"name": "Boolean project custom field",
"required": false,
"hasDefault": false,
"writable": true,
"visibility": "default"
},
"customField35": {
"type": "String",
"name": "Text project custom field",
"required": true,
"hasDefault": false,
"writable": true,
"visibility": "default"
},
"_links": {
"self": {
"href": "/api/v3/projects/schema"
}
}
}
## View project schema [GET]
+ Response 200 (application/hal+json)
[Projects schema][]
## Projects by version [/api/v3/versions/{id}/projects]

@ -31,7 +31,7 @@
module API
module Decorators
class AggregationGroup < Single
def initialize(group_key, count, query:, sums: nil)
def initialize(group_key, count, query:, sums: nil, current_user:)
@count = count
@sums = sums
@query = query
@ -42,7 +42,7 @@ module API
@link = ::API::V3::Utilities::ResourceLinkGenerator.make_link(group_key)
super(group_key, current_user: nil)
super(group_key, current_user: current_user)
end
links :valueLink do
@ -76,7 +76,7 @@ module API
property :sums,
exec_context: :decorator,
getter: ->(*) {
::API::V3::WorkPackages::WorkPackageSumsRepresenter.create(sums) if sums
::API::V3::WorkPackages::WorkPackageSumsRepresenter.create(sums, current_user) if sums
},
render_nil: false

@ -105,8 +105,8 @@ module API
end
module ClassMethods
def create_class(work_package)
new_class = super
def create_class(*args)
new_class = super(*args)
new_class.send(:include, ::API::Utilities::PayloadRepresenter)

@ -93,10 +93,13 @@ module API
{ href: api_v3_paths.types_by_project(represented.id) }
end
property :id, render_nil: true
property :identifier, render_nil: true
property :id
property :identifier
property :name
property :status
property :is_public,
as: :public
property :name, render_nil: true
formattable_property :description,
uncacheable: true

@ -37,10 +37,11 @@ module API
scope: -> { Project.visible(User.current).includes(:enabled_modules) })
.mount
mount ::API::V3::Projects::Schemas::ProjectSchemaAPI
params do
requires :id, desc: 'Project id'
end
route_param :id do
after_validation do
@project = Project.find(params[:id])

@ -0,0 +1,41 @@
#-- 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.
#++
module API
module V3
module Projects
module Schemas
class ProjectSchemaAPI < ::API::OpenProjectAPI
resources :schema do
get &::API::V3::Utilities::Endpoints::Schema.new(model: Project).mount
end
end
end
end
end
end

@ -0,0 +1,83 @@
#-- 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.
#++
module API
module V3
module Projects
module Schemas
class ProjectSchemaRepresenter < ::API::Decorators::SchemaRepresenter
extend ::API::V3::Utilities::CustomFieldInjector::RepresenterClass
custom_field_injector type: :schema_representer
schema :id,
type: 'Integer',
visibility: false
schema :name,
type: 'String',
visibility: false,
min_length: 1,
max_length: 255
schema :identifier,
type: 'String',
visibility: false,
min_length: 1,
max_length: 100
schema :description,
type: 'Formattable',
visibility: false,
required: false
schema :is_public,
as: :public,
type: 'Boolean',
visibility: false
schema_with_allowed_string_collection :status,
type: 'String'
schema :created_at,
type: 'DateTime',
visibility: false
schema :updated_at,
type: 'DateTime',
visibility: false
def self.represented_class
::Project
end
end
end
end
end
end

@ -32,7 +32,6 @@ module API
module Schemas
class SchemaCollectionRepresenter <
::API::Decorators::UnpaginatedCollection
def initialize(represented, self_link, current_user:, form_embedded: false)
self.form_embedded = form_embedded

@ -353,12 +353,18 @@ module API
injector_class: ::API::V3::Utilities::CustomFieldInjector }
end
def create_class(represented)
custom_field_class(represented.available_custom_fields)
def create_class(represented, current_user)
custom_fields = if current_user.admin?
represented.available_custom_fields
else
represented.available_custom_fields.select(&:visible?)
end
custom_field_class(custom_fields)
end
def create(*args)
create_class(args.first)
create_class(args.first, args.last[:current_user])
.new(*args)
end

@ -65,9 +65,9 @@ module API
contract_instance = contract.new(instance, User.current)
representer
.new(contract_instance,
self_path,
current_user: User.current)
.create(contract_instance,
self_path,
current_user: User.current)
end
def self_path

@ -211,6 +211,7 @@ module API
index :project
show :project
schema :project
resources :query

@ -42,7 +42,7 @@ module API
def payload_representer
WorkPackagePayloadRepresenter
.create_class(represented)
.create_class(represented, current_user)
.new(represented, current_user: current_user)
end

@ -39,7 +39,7 @@ module API
def parse_attributes(request_body)
::API::V3::WorkPackages::WorkPackagePayloadRepresenter
.create_class(struct)
.create_class(struct, current_user)
.new(struct, current_user: current_user)
.from_hash(Hash(request_body))
.to_h

@ -83,7 +83,7 @@ module API
WorkPackageSchemaCollectionRepresenter.new(schemas,
schemas_path_with_filters_params,
current_user: nil)
current_user: current_user)
end
# The schema identifier is an artificial identifier that is composed of a work package's
@ -114,7 +114,7 @@ module API
self_link = api_v3_paths.work_package_schema(@project.id, @type.id)
represented_schema = WorkPackageSchemaRepresenter.create(schema,
self_link,
current_user: nil)
current_user: current_user)
with_etag! represented_schema.json_cache_key

@ -158,7 +158,7 @@ module API
exec_context: :decorator,
getter: ->(*) {
if total_sums
::API::V3::WorkPackages::WorkPackageSumsRepresenter.create(total_sums)
::API::V3::WorkPackages::WorkPackageSumsRepresenter.create(total_sums, current_user)
end
},
render_nil: false

@ -13,8 +13,8 @@ module API
super(sums, current_user: nil)
end
def self.create(sums)
create_class(Schema::WorkPackageSumsSchema.new).new(sums)
def self.create(sums, current_user)
create_class(Schema::WorkPackageSumsSchema.new, current_user).new(sums)
end
property :estimated_time,

@ -29,9 +29,9 @@
require 'spec_helper'
describe ::API::V3::WorkPackages::Schema::WorkPackageSumsSchemaRepresenter do
let(:current_user) {
let(:current_user) do
FactoryBot.build_stubbed(:user)
}
end
let(:schema) { ::API::V3::WorkPackages::Schema::WorkPackageSumsSchema.new }

@ -31,8 +31,9 @@ require 'spec_helper'
describe ::API::V3::WorkPackages::WorkPackageSumsRepresenter do
let(:sums) { double 'sums', story_points: 5, remaining_hours: 10 }
let(:schema) { double 'schema', available_custom_fields: [] }
let(:user) { FactoryBot.build_stubbed(:user) }
let(:representer) {
described_class.create_class(schema).new(sums)
described_class.create_class(schema, user).new(sums)
}
let(:summable_columns) { [] }

@ -31,8 +31,9 @@ require 'spec_helper'
describe ::API::V3::WorkPackages::WorkPackageSumsRepresenter do
let(:sums) { double 'sums', material_costs: 5, labor_costs: 10, overall_costs: 15 }
let(:schema) { double 'schema', available_custom_fields: [] }
let(:user) { FactoryBot.build_stubbed(:user) }
let(:representer) {
described_class.create_class(schema).new(sums)
described_class.create_class(schema, user).new(sums)
}
let(:summable_columns) { [] }

@ -0,0 +1,45 @@
#-- 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.
#++
require 'spec_helper'
require_relative './shared_contract_examples'
describe Projects::CreateContract do
it_behaves_like 'project contract' do
let(:project) do
Project.new(name: project_name,
identifier: project_identifier,
description: project_description,
status: project_status,
is_public: project_public,
parent: project_parent)
end
subject(:contract) { described_class.new(project, current_user) }
end
end

@ -0,0 +1,171 @@
#-- 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.
#++
require 'spec_helper'
shared_examples_for 'project contract' do
let(:current_user) do
FactoryBot.build_stubbed(:user)
end
let(:project_name) { 'Project name' }
let(:project_identifier) { 'project_identifier' }
let(:project_description) { 'Project description' }
let(:project_status) { Project::STATUS_ACTIVE }
let(:project_public) { true }
let(:project_parent) do
FactoryBot.build_stubbed(:project).tap do |p|
allow(p)
.to receive(:visible?)
.and_return(parent_visible)
end
end
let(:parent_visible) { true }
def expect_valid(valid, symbols = {})
expect(contract.validate).to eq(valid)
symbols.each do |key, arr|
expect(contract.errors.symbols_for(key)).to match_array arr
end
end
shared_examples 'is valid' do
it 'is valid' do
expect_valid(true)
end
end
it_behaves_like 'is valid'
context 'if the name is nil' do
let(:project_name) { nil }
it 'is invalid' do
expect_valid(false, name: %i(blank))
end
end
context 'if the identifier is nil' do
let(:project_identifier) { nil }
it 'is invalid' do
expect_valid(false, identifier: %i(blank too_short))
end
end
context 'if the description is nil' do
let(:project_description) { nil }
it_behaves_like 'is valid'
end
context 'if the parent is nil' do
let(:project_parent) { nil }
it_behaves_like 'is valid'
end
context 'if the parent is invisible to the user' do
let(:parent_visible) { false }
it 'is invalid' do
expect_valid(false, parent: %i(does_not_exist))
end
end
context 'if the status is nil' do
let(:project_status) { nil }
it 'is invalid' do
expect_valid(false, status: %i(blank))
end
end
describe 'assignable_values' do
context 'for project' do
let(:visible_projects) { double('visible projects') }
before do
allow(Project)
.to receive(:visible)
.and_return(visible_projects)
end
it 'is the list of visible projects' do
expect(subject.assignable_values(:parent, current_user))
.to eql visible_projects
end
end
context 'for status' do
it 'is a list of all available status' do
expect(subject.assignable_values(:status, current_user))
.to eql Project.statuses.keys
end
end
context 'for a custom field' do
let(:custom_field) { FactoryBot.build_stubbed(:list_project_custom_field) }
it 'is the list of custom field values' do
expect(subject.assignable_custom_field_values(custom_field))
.to eql custom_field.possible_values
end
end
end
describe 'available_custom_fields' do
let(:visible_custom_field) { FactoryBot.build_stubbed(:int_project_custom_field, visible: true) }
let(:invisible_custom_field) { FactoryBot.build_stubbed(:int_project_custom_field, visible: false) }
before do
allow(project)
.to receive(:available_custom_fields)
.and_return([visible_custom_field, invisible_custom_field])
end
context 'if the user is admin' do
before do
allow(current_user)
.to receive(:admin?)
.and_return(true)
end
it 'returns all available_custom_fields of the project' do
expect(subject.available_custom_fields)
.to match_array([visible_custom_field, invisible_custom_field])
end
end
context 'if the user is no admin' do
it 'returns all visible and available_custom_fields of the project' do
expect(subject.available_custom_fields)
.to match_array([visible_custom_field])
end
end
end
end

@ -0,0 +1,47 @@
#-- 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.
#++
require 'spec_helper'
require_relative './shared_contract_examples'
describe Projects::UpdateContract do
it_behaves_like 'project contract' do
let(:project) do
FactoryBot.build_stubbed(:project,
identifier: project_identifier,
status: project_status,
is_public: project_public).tap do |p|
# in order to actually have something changed
p.name = project_name
p.parent = project_parent
end
end
subject(:contract) { described_class.new(project, current_user) }
end
end

@ -162,10 +162,10 @@ describe ProjectsController, type: :controller do
end
describe 'index.html' do
let(:project_a) { FactoryBot.create(:project, name: 'Project A', is_public: false, status: true) }
let(:project_b) { FactoryBot.create(:project, name: 'Project B', is_public: false, status: true) }
let(:project_c) { FactoryBot.create(:project, name: 'Project C', is_public: true, status: true) }
let(:project_d) { FactoryBot.create(:project, name: 'Project D', is_public: true, status: false) }
let(:project_a) { FactoryBot.create(:project, name: 'Project A', is_public: false, status: :active) }
let(:project_b) { FactoryBot.create(:project, name: 'Project B', is_public: false, status: :active) }
let(:project_c) { FactoryBot.create(:project, name: 'Project C', is_public: true, status: :active) }
let(:project_d) { FactoryBot.create(:project, name: 'Project D', is_public: true, status: :archived) }
let(:projects) { [project_a, project_b, project_c, project_d] }

@ -37,8 +37,9 @@ describe ::API::Decorators::AggregationGroup do
end
let(:group_key) { OpenStruct.new name: 'ABC' }
let(:count) { 5 }
let(:current_user) { FactoryBot.build_stubbed(:user) }
subject { described_class.new(group_key, count, query: query).to_json }
subject { described_class.new(group_key, count, query: query, current_user: current_user).to_json }
context 'with an empty array key' do
let(:group_key) { [] }

@ -59,8 +59,8 @@ describe ::API::V3::Projects::ProjectRepresenter do
end
end
let(:int_custom_field) { FactoryBot.build_stubbed(:int_project_custom_field) }
let(:version_custom_field) { FactoryBot.build_stubbed(:version_project_custom_field) }
let(:int_custom_field) { FactoryBot.build_stubbed(:int_project_custom_field, visible: false) }
let(:version_custom_field) { FactoryBot.build_stubbed(:version_project_custom_field, visible: true) }
let(:int_custom_value) do
CustomValue.new(custom_field: int_custom_field,
value: '1234',
@ -101,6 +101,14 @@ describe ::API::V3::Projects::ProjectRepresenter do
let(:value) { project.name }
end
it_behaves_like 'property', :status do
let(:value) { project.status }
end
it_behaves_like 'property', :public do
let(:value) { project.is_public }
end
it_behaves_like 'formattable property', :description do
let(:value) { project.description }
end
@ -115,10 +123,27 @@ describe ::API::V3::Projects::ProjectRepresenter do
let(:json_path) { 'updatedAt' }
end
it "has a property for the int custom field" do
is_expected
.to be_json_eql(int_custom_value.value.to_json)
.at_path("customField#{int_custom_field.id}")
context 'int custom field' do
context 'if the user is admin' do
before do
allow(user)
.to receive(:admin?)
.and_return(true)
end
it "has a property for the int custom field" do
is_expected
.to be_json_eql(int_custom_value.value.to_json)
.at_path("customField#{int_custom_field.id}")
end
end
context 'if the user is no admin' do
it "has no property for the int custom field" do
is_expected
.not_to have_json_path("customField#{int_custom_field.id}")
end
end
end
end
@ -245,10 +270,41 @@ describe ::API::V3::Projects::ProjectRepresenter do
end
end
it 'links custom fields' do
is_expected
.to be_json_eql(api_v3_paths.version(version.id).to_json)
.at_path("_links/customField#{version_custom_field.id}/href")
context 'link custom field' do
context 'if the user is admin and the field is invisible' do
before do
allow(user)
.to receive(:admin?)
.and_return(true)
version_custom_field.visible = false
end
it 'links custom fields' do
is_expected
.to be_json_eql(api_v3_paths.version(version.id).to_json)
.at_path("_links/customField#{version_custom_field.id}/href")
end
end
context 'if the user is no admin and the field is invisible' do
before do
version_custom_field.visible = false
end
it "has no property for the int custom field" do
is_expected
.not_to have_json_path("links/customField#{version_custom_field.id}")
end
end
context 'if the user is no admin and the field is visible' do
it 'links custom fields' do
is_expected
.to be_json_eql(api_v3_paths.version(version.id).to_json)
.at_path("_links/customField#{version_custom_field.id}/href")
end
end
end
end

@ -0,0 +1,233 @@
#-- 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.
#++
require 'spec_helper'
describe ::API::V3::Projects::Schemas::ProjectSchemaRepresenter do
include API::V3::Utilities::PathHelper
let(:current_user) { FactoryBot.build_stubbed(:user) }
let(:self_link) { '/a/self/link' }
let(:embedded) { true }
let(:new_record) { true }
let(:custom_field) do
FactoryBot.build_stubbed(:int_project_custom_field)
end
let(:allowed_status) { ['some status'] }
let(:contract) do
contract = double('contract')
allow(contract)
.to receive(:writable?) do |attribute|
writable = %w(name identifier description is_public status)
writable.include?(attribute.to_s)
end
allow(contract)
.to receive(:available_custom_fields)
.and_return([custom_field])
allow(contract)
.to receive(:assignable_values)
.with(:status, current_user)
.and_return(allowed_status)
contract
end
let(:representer) do
described_class.create(contract,
self_link,
form_embedded: embedded,
current_user: current_user)
end
context 'generation' do
subject(:generated) { representer.to_json }
describe '_type' do
it 'is indicated as Schema' do
is_expected.to be_json_eql('Schema'.to_json).at_path('_type')
end
end
describe 'id' do
let(:path) { 'id' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'Integer' }
let(:name) { I18n.t('attributes.id') }
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'name' do
let(:path) { 'name' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'String' }
let(:name) { I18n.t('attributes.name') }
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'indicates length requirements' do
let(:min_length) { 1 }
let(:max_length) { 255 }
end
it_behaves_like 'has no visibility property'
end
describe 'identifier' do
let(:path) { 'identifier' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'String' }
let(:name) { I18n.t('activerecord.attributes.project.identifier') }
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'indicates length requirements' do
let(:min_length) { 1 }
let(:max_length) { 100 }
end
it_behaves_like 'has no visibility property'
end
describe 'description' do
let(:path) { 'description' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'Formattable' }
let(:name) { I18n.t('attributes.description') }
let(:required) { false }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'public' do
let(:path) { 'public' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'Boolean' }
let(:name) { I18n.t('attributes.is_public') }
let(:required) { true }
let(:writable) { true }
end
it_behaves_like 'has no visibility property'
end
describe 'status' do
let(:path) { 'status' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'String' }
let(:name) { I18n.t('attributes.status') }
let(:required) { true }
let(:writable) { true }
end
it 'contains no link to the allowed values' do
is_expected
.not_to have_json_path("#{path}/_links/allowedValues")
end
it 'embeds the allowed values' do
allowed_path = "#{path}/_embedded/allowedValues"
is_expected
.to be_json_eql(allowed_status.to_json)
.at_path(allowed_path)
end
end
describe 'createdAt' do
let(:path) { 'createdAt' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'DateTime' }
let(:name) { I18n.t('attributes.created_at') }
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'updatedAt' do
let(:path) { 'updatedAt' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'DateTime' }
let(:name) { I18n.t('attributes.updated_at') }
let(:required) { true }
let(:writable) { false }
end
it_behaves_like 'has no visibility property'
end
describe 'int custom field' do
let(:path) { "customField#{custom_field.id}" }
it_behaves_like 'has basic schema properties' do
let(:type) { 'Integer' }
let(:name) { custom_field.name }
let(:required) { false }
let(:writable) { true }
end
end
context '_links' do
describe 'self link' do
it_behaves_like 'has an untitled link' do
let(:link) { 'self' }
let(:href) { self_link }
end
context 'embedded in a form' do
let(:self_link) { nil }
it_behaves_like 'has no link' do
let(:link) { 'self' }
end
end
end
end
end
end

@ -240,6 +240,7 @@ describe ::API::V3::Utilities::PathHelper do
describe 'projects paths' do
it_behaves_like 'index', :project
it_behaves_like 'show', :project
it_behaves_like 'schema', :project
end
describe 'query paths' do

@ -33,7 +33,7 @@ describe ::API::V3::WorkPackages::Schema::WorkPackageSumsSchemaRepresenter do
let(:available_custom_fields) { [] }
let(:schema) { double('wp_schema', available_custom_fields: available_custom_fields) }
let(:current_user) { double('user') }
let(:current_user) { double('user', admin?: false) }
let(:representer) do
described_class.create(schema, current_user: current_user)

@ -32,8 +32,9 @@ describe ::API::V3::WorkPackages::WorkPackageSumsRepresenter do
let(:available_custom_fields) { [] }
let(:sums) { double 'sums', estimated_hours: 5 }
let(:schema) { double 'schema', available_custom_fields: available_custom_fields }
let(:current_user) { FactoryBot.build_stubbed(:user) }
let(:representer) do
described_class.create_class(schema).new(sums)
described_class.create_class(schema, current_user).new(sums)
end
let(:summable_columns) { [] }

@ -44,18 +44,13 @@ describe Project, type: :model do
end
describe '#active?' do
before do
# stub out the actual value of the constant
stub_const('Project::STATUS_ACTIVE', 42)
end
it 'is active when :status equals STATUS_ACTIVE' do
project = FactoryBot.build :project, status: 42
project = FactoryBot.build :project, status: :active
expect(project).to be_active
end
it "is not active when :status doesn't equal STATUS_ACTIVE" do
project = FactoryBot.build :project, status: 99
project = FactoryBot.build :project, status: :archived
expect(project).not_to be_active
end
end
@ -85,23 +80,23 @@ describe Project, type: :model do
let(:role_copy_projects) { FactoryBot.create(:role, permissions: [:edit_project, :copy_projects, :add_project]) }
let(:parent_project) { FactoryBot.create(:project) }
let(:project) { FactoryBot.create(:project, parent: parent_project) }
let!(:subproject_member) {
let!(:subproject_member) do
FactoryBot.create(:member,
user: user,
project: project,
roles: [role_copy_projects])
}
end
before do
login_as(user)
end
context 'with permission to add subprojects' do
let!(:member_add_subproject) {
let!(:member_add_subproject) do
FactoryBot.create(:member,
user: user,
project: parent_project,
roles: [role_add_subproject])
}
end
it 'should allow copy' do
expect(project.copy_allowed?).to eq(true)
@ -119,18 +114,18 @@ describe Project, type: :model do
let(:user) { FactoryBot.create(:user) }
let(:group) { FactoryBot.create(:group) }
let(:role) { FactoryBot.create(:role) }
let!(:user_member) {
let!(:user_member) do
FactoryBot.create(:member,
principal: user,
project: project,
roles: [role])
}
let!(:group_member) {
principal: user,
project: project,
roles: [role])
end
let!(:group_member) do
FactoryBot.create(:member,
principal: group,
project: project,
roles: [role])
}
principal: group,
project: project,
roles: [role])
end
shared_examples_for 'respecting group assignment settings' do
context 'with group assignment' do

@ -0,0 +1,68 @@
#-- 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.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Projects schema resource', type: :request, content_type: :json do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:current_user) do
FactoryBot.create(:user)
end
let(:path) { api_v3_paths.project_schema }
before do
login_as(current_user)
end
subject(:response) { last_response }
describe '#GET /projects/schema' do
before do
get path
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'returns a schema' do
expect(subject.body)
.to be_json_eql('Schema'.to_json)
.at_path '_type'
end
#it 'does not embed' do
# expect(subject.body)
# .not_to have_json_path('page/_links/allowedValues')
#end
end
end

@ -115,16 +115,19 @@ describe ::API::V3::WorkPackageCollectionFromQueryService,
Struct.new(:group,
:count,
:query,
:sums) do
:sums,
:current_user) do
def initialize(group,
count,
query:,
sums:)
sums:,
current_user:)
super(group,
count,
query,
sums)
sums,
current_user)
end
end
end

@ -654,7 +654,7 @@ describe Project, type: :model do
assert_equal source_project.types, copied_project.types
# Default attributes
assert_equal 1, copied_project.status
assert_equal "active", copied_project.status
end
it 'should activities should use the system activities' do
@ -757,11 +757,11 @@ describe Project, type: :model do
it 'should copy work units' do
work_package = FactoryBot.create(:work_package,
status: Status.find_by_name('Closed'),
subject: 'copy issue status',
type_id: 1,
assigned_to_id: 2,
project_id: @source_project.id)
status: Status.find_by_name('Closed'),
subject: 'copy issue status',
type_id: 1,
assigned_to_id: 2,
project_id: @source_project.id)
@source_project.work_packages << work_package
assert @project.valid?

@ -26,7 +26,7 @@
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'legacy_spec_helper'
require_relative '../legacy_spec_helper'
describe User, type: :model do
include MiniTest::Assertions # refute
@ -386,7 +386,7 @@ describe User, type: :model do
context 'with a unique project' do
it 'should return false if project is archived' do
project = Project.find(1)
allow_any_instance_of(Project).to receive(:status).and_return(Project::STATUS_ARCHIVED)
allow_any_instance_of(Project).to receive(:active?).and_return(false)
assert ! @admin.allowed_to?(:view_work_packages, Project.find(1))
end

Loading…
Cancel
Save