mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
692 lines
20 KiB
Ruby
692 lines
20 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#-- copyright
|
|
# OpenProject is an open source project management software.
|
|
# Copyright (C) the OpenProject GmbH
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
#
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
|
# Copyright (C) 2006-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 COPYRIGHT and LICENSE files for more details.
|
|
#++
|
|
|
|
require "spec_helper"
|
|
require "rack/test"
|
|
|
|
RSpec.describe "API v3 Version resource", content_type: :json do
|
|
include Rack::Test::Methods
|
|
include API::V3::Utilities::PathHelper
|
|
|
|
let(:current_user) do
|
|
create(:user, member_with_permissions: { project => permissions })
|
|
end
|
|
let(:permissions) { %i[view_work_packages manage_versions] }
|
|
let(:project) { create(:project, public: false) }
|
|
let(:other_project) { create(:project, public: false) }
|
|
let!(:int_cf) { create(:version_custom_field, :integer) }
|
|
let(:version_in_project) { build(:version, project:, custom_field_values: { int_cf.id => 123 }) }
|
|
let(:version_in_other_project) do
|
|
build(:version,
|
|
project: other_project,
|
|
sharing: "system",
|
|
custom_field_values: { int_cf.id => 123 })
|
|
end
|
|
|
|
subject(:response) { last_response }
|
|
|
|
describe "GET api/v3/versions/:id" do
|
|
let(:get_path) { api_v3_paths.version version_in_project.id }
|
|
|
|
shared_examples_for "successful response" do
|
|
it "responds with 200" do
|
|
expect(last_response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "returns the version" do
|
|
expect(last_response.body)
|
|
.to be_json_eql("Version".to_json)
|
|
.at_path("_type")
|
|
|
|
expect(last_response.body)
|
|
.to be_json_eql(expected_version.id.to_json)
|
|
.at_path("id")
|
|
|
|
expect(last_response.body)
|
|
.to be_json_eql(123.to_json)
|
|
.at_path("customField#{int_cf.id}")
|
|
end
|
|
end
|
|
|
|
context "for a logged in user with permissions" do
|
|
before do
|
|
version_in_project.save!
|
|
login_as current_user
|
|
|
|
get get_path
|
|
end
|
|
|
|
it_behaves_like "successful response" do
|
|
let(:expected_version) { version_in_project }
|
|
end
|
|
end
|
|
|
|
context "for a logged in user with permission on project a version is shared with" do
|
|
let(:get_path) { api_v3_paths.version version_in_other_project.id }
|
|
|
|
before do
|
|
version_in_other_project.save!
|
|
login_as current_user
|
|
|
|
get get_path
|
|
end
|
|
|
|
it_behaves_like "successful response" do
|
|
let(:expected_version) { version_in_other_project }
|
|
end
|
|
end
|
|
|
|
context "for a logged in user without permission" do
|
|
let(:permissions) { [] }
|
|
|
|
before do
|
|
version_in_project.save!
|
|
login_as current_user
|
|
|
|
get get_path
|
|
end
|
|
|
|
it_behaves_like "not found"
|
|
end
|
|
end
|
|
|
|
describe "PATCH api/v3/versions" do
|
|
let(:path) { api_v3_paths.version(version.id) }
|
|
let(:version) do
|
|
create(:version,
|
|
:skip_validations,
|
|
name: "Old name",
|
|
description: "Old description",
|
|
start_date: "2017-06-01",
|
|
effective_date: "2017-07-01",
|
|
status: "open",
|
|
sharing: "none",
|
|
project:,
|
|
custom_field_values: { int_cf.id => 123,
|
|
list_cf.id => list_cf.custom_options.first.id })
|
|
end
|
|
let!(:int_cf) { create(:version_custom_field, :integer) }
|
|
let!(:list_cf) { create(:version_custom_field, :list) }
|
|
let(:body) do
|
|
{
|
|
name: "New name",
|
|
description: {
|
|
raw: "New description"
|
|
},
|
|
"customField#{int_cf.id}": 5,
|
|
startDate: "2018-01-01",
|
|
endDate: "2018-01-09",
|
|
status: "closed",
|
|
sharing: "descendants",
|
|
_links: {
|
|
"customField#{list_cf.id}": {
|
|
href: api_v3_paths.custom_option(list_cf.custom_options.last.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
before do
|
|
login_as current_user
|
|
end
|
|
|
|
subject(:response) { patch path, body }
|
|
|
|
it "responds with 200" do
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "updates the version" do
|
|
response
|
|
expect(Version.find_by(name: "New name"))
|
|
.to be_present
|
|
end
|
|
|
|
it "returns the updated version", :aggregate_failures do
|
|
expected_attributes = {
|
|
"_type" => "Version",
|
|
"name" => "New name",
|
|
"description/html" => "<p>New description</p>",
|
|
"startDate" => "2018-01-01",
|
|
"endDate" => "2018-01-09",
|
|
"status" => "closed",
|
|
"sharing" => "descendants",
|
|
"_links/definingProject/title" => project.name,
|
|
"_links/customField#{list_cf.id}/href" => api_v3_paths.custom_option(list_cf.custom_options.last.id),
|
|
"customField#{int_cf.id}" => 5
|
|
}
|
|
|
|
expected_attributes.each do |path, value|
|
|
expect(response.body)
|
|
.to be_json_eql(value.to_json)
|
|
.at_path(path)
|
|
end
|
|
end
|
|
|
|
describe "custom fields" do
|
|
context "with a required custom field" do
|
|
let!(:required_custom_field) do
|
|
create(:version_custom_field, :string,
|
|
name: "Release Notes",
|
|
is_required: true)
|
|
end
|
|
|
|
context "when no custom field value is provided" do
|
|
it "responds with 200" do
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "keeps the custom field value to be empty" do
|
|
response
|
|
expect(version.send(:"custom_field_#{required_custom_field.id}"))
|
|
.to be_nil
|
|
end
|
|
end
|
|
|
|
context "when the custom field value is provided but empty" do
|
|
let(:body) do
|
|
{
|
|
name: "Updated version",
|
|
"customField#{required_custom_field.id}" => "",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "returns 422 with custom field validation error" do
|
|
expect(response)
|
|
.to have_http_status(422)
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Release Notes can't be blank.".to_json)
|
|
.at_path("message")
|
|
end
|
|
|
|
it "does not alter the version" do
|
|
response
|
|
expect(version.reload.name)
|
|
.not_to eq("Updated version")
|
|
end
|
|
end
|
|
|
|
context "when the custom field value is being cleared" do
|
|
before do
|
|
# Set an initial value for the custom field
|
|
version.custom_field_values = { required_custom_field.id => "Initial release notes" }
|
|
version.save!
|
|
end
|
|
|
|
let(:body) do
|
|
{
|
|
name: "Updated version",
|
|
"customField#{required_custom_field.id}" => "",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "returns 422 with custom field validation error" do
|
|
expect(response)
|
|
.to have_http_status(422)
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Release Notes can't be blank.".to_json)
|
|
.at_path("message")
|
|
end
|
|
|
|
it "does not alter the version" do
|
|
version.reload
|
|
expect(version.name).not_to eq("Updated version")
|
|
|
|
# Custom field value should remain unchanged
|
|
expect(version.typed_custom_value_for(required_custom_field)).to eq("Initial release notes")
|
|
end
|
|
end
|
|
|
|
context "when the custom field value is provided and valid" do
|
|
before do
|
|
# Set an initial value for the custom field
|
|
version.custom_field_values = { required_custom_field.id => "Initial release notes" }
|
|
version.save!
|
|
end
|
|
|
|
let(:body) do
|
|
{
|
|
name: "New version with valid CF",
|
|
"customField#{required_custom_field.id}" => "Bug fixes and improvements",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "responds with 201" do
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "creates the version with custom field value" do
|
|
response
|
|
version = Version.find_by(name: "New version with valid CF")
|
|
expect(version).to be_present
|
|
|
|
expect(version.typed_custom_value_for(required_custom_field)).to eq("Bug fixes and improvements")
|
|
end
|
|
|
|
it "returns the newly created version" do
|
|
expect(response.body)
|
|
.to be_json_eql("Version".to_json)
|
|
.at_path("_type")
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("New version with valid CF".to_json)
|
|
.at_path("name")
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Bug fixes and improvements".to_json)
|
|
.at_path("customField#{required_custom_field.id}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if attempting to switch the project" do
|
|
let(:other_project) do
|
|
create(:project).tap do |p|
|
|
create(:member,
|
|
project: p,
|
|
roles: [create(:project_role, permissions: [:manage_versions])],
|
|
user: current_user)
|
|
end
|
|
end
|
|
|
|
let(:other_membership) do
|
|
end
|
|
let(:body) do
|
|
{
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(other_project.id)
|
|
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "read-only violation", "project", Version
|
|
end
|
|
|
|
context "if lacking the manage permissions but having view permission" do
|
|
let(:permissions) { [:view_work_packages] }
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "unauthorized access"
|
|
end
|
|
|
|
context "if lacking manage and view permissions" do
|
|
let(:permissions) { [] }
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "not found"
|
|
end
|
|
|
|
context "if having the manage permission in a different project" do
|
|
let(:other_membership) do
|
|
create(:member,
|
|
project: create(:project),
|
|
roles: [create(:project_role, permissions: [:manage_versions])])
|
|
end
|
|
|
|
let(:permissions) do
|
|
# load early
|
|
other_membership
|
|
|
|
[:view_work_packages]
|
|
end
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "unauthorized access"
|
|
end
|
|
end
|
|
|
|
describe "POST api/v3/versions" do
|
|
let(:path) { api_v3_paths.versions }
|
|
let!(:int_cf) { create(:version_custom_field, :integer) }
|
|
let!(:list_cf) { create(:version_custom_field, :list) }
|
|
let(:body) do
|
|
{
|
|
name: "New version",
|
|
description: {
|
|
raw: "A new description"
|
|
},
|
|
"customField#{int_cf.id}": 5,
|
|
startDate: "2018-01-01",
|
|
endDate: "2018-01-09",
|
|
status: "closed",
|
|
sharing: "descendants",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
},
|
|
"customField#{list_cf.id}": {
|
|
href: api_v3_paths.custom_option(list_cf.custom_options.first.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
before do
|
|
login_as current_user
|
|
end
|
|
|
|
subject(:response) { post path, body }
|
|
|
|
it "responds with 201" do
|
|
expect(response).to have_http_status(:created)
|
|
end
|
|
|
|
it "creates the version" do
|
|
response
|
|
expect(Version.find_by(name: "New version"))
|
|
.to be_present
|
|
end
|
|
|
|
it "returns the newly created version", :aggregate_failures do
|
|
expected_attributes = {
|
|
"_type" => "Version",
|
|
"name" => "New version",
|
|
"description/html" => "<p>A new description</p>",
|
|
"startDate" => "2018-01-01",
|
|
"endDate" => "2018-01-09",
|
|
"status" => "closed",
|
|
"sharing" => "descendants",
|
|
"_links/definingProject/title" => project.name,
|
|
"_links/customField#{list_cf.id}/href" => api_v3_paths.custom_option(list_cf.custom_options.first.id),
|
|
"customField#{int_cf.id}" => 5
|
|
}
|
|
|
|
expected_attributes.each do |path, value|
|
|
expect(response.body)
|
|
.to be_json_eql(value.to_json)
|
|
.at_path(path)
|
|
end
|
|
end
|
|
|
|
describe "custom fields" do
|
|
context "with a required custom field" do
|
|
let!(:required_custom_field) do
|
|
create(:version_custom_field, :string,
|
|
name: "Release Notes",
|
|
is_required: true)
|
|
end
|
|
|
|
context "when no custom field value is provided" do
|
|
let(:body) do
|
|
{
|
|
name: "New version with CF",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "responds with 422 and explains the custom field error" do
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Release Notes can't be blank.".to_json)
|
|
.at_path("message")
|
|
end
|
|
end
|
|
|
|
context "when the custom field value is provided but empty" do
|
|
let(:body) do
|
|
{
|
|
name: "New version with CF",
|
|
"customField#{required_custom_field.id}" => "",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "responds with 422 and explains the custom field error" do
|
|
expect(response).to have_http_status(:unprocessable_entity)
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Release Notes can't be blank.".to_json)
|
|
.at_path("message")
|
|
end
|
|
end
|
|
|
|
context "when the custom field value is provided and valid" do
|
|
let(:body) do
|
|
{
|
|
name: "New version with valid CF",
|
|
"customField#{required_custom_field.id}" => "Bug fixes and improvements",
|
|
_links: {
|
|
definingProject: {
|
|
href: api_v3_paths.project(project.id)
|
|
}
|
|
}
|
|
}.to_json
|
|
end
|
|
|
|
it "responds with 201" do
|
|
expect(response).to have_http_status(:created)
|
|
end
|
|
|
|
it "creates the version with custom field value" do
|
|
response
|
|
version = Version.find_by(name: "New version with valid CF")
|
|
expect(version).to be_present
|
|
|
|
expect(version.typed_custom_value_for(required_custom_field)).to eq("Bug fixes and improvements")
|
|
end
|
|
|
|
it "returns the newly created version" do
|
|
expect(response.body)
|
|
.to be_json_eql("Version".to_json)
|
|
.at_path("_type")
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("New version with valid CF".to_json)
|
|
.at_path("name")
|
|
|
|
expect(response.body)
|
|
.to be_json_eql("Bug fixes and improvements".to_json)
|
|
.at_path("customField#{required_custom_field.id}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "if lacking the manage permissions" do
|
|
let(:permissions) { [] }
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "unauthorized access"
|
|
end
|
|
|
|
context "if having the manage permission in a different project" do
|
|
let(:other_membership) do
|
|
create(:member,
|
|
project: create(:project),
|
|
roles: [create(:project_role, permissions: [:manage_versions])])
|
|
end
|
|
|
|
let(:permissions) do
|
|
# load early
|
|
other_membership
|
|
|
|
[:view_work_packages]
|
|
end
|
|
|
|
before { response }
|
|
|
|
it_behaves_like "unauthorized access"
|
|
end
|
|
end
|
|
|
|
describe "GET api/v3/versions" do
|
|
let(:get_path) { api_v3_paths.versions }
|
|
let(:response) { last_response }
|
|
let(:versions) { [version_in_project] }
|
|
|
|
before do
|
|
versions.map(&:save!)
|
|
login_as current_user
|
|
|
|
get get_path
|
|
end
|
|
|
|
it "succeeds" do
|
|
expect(last_response)
|
|
.to have_http_status(200)
|
|
end
|
|
|
|
it_behaves_like "API V3 collection response", 1, 1, "Version"
|
|
|
|
it "is the version the user has permission in" do
|
|
expect(response.body)
|
|
.to be_json_eql(api_v3_paths.version(version_in_project.id).to_json)
|
|
.at_path("_embedded/elements/0/_links/self/href")
|
|
end
|
|
|
|
context "filtering for project by sharing" do
|
|
let(:shared_version_in_project) do
|
|
build(:version, project:, sharing: "system")
|
|
end
|
|
let(:versions) { [version_in_project, shared_version_in_project] }
|
|
|
|
let(:filter_query) do
|
|
[{ sharing: { operator: "=", values: ["system"] } }]
|
|
end
|
|
|
|
let(:get_path) do
|
|
"#{api_v3_paths.versions}?filters=#{CGI.escape(JSON.dump(filter_query))}"
|
|
end
|
|
|
|
it_behaves_like "API V3 collection response", 1, 1, "Version"
|
|
|
|
it "returns the shared version" do
|
|
expect(response.body)
|
|
.to be_json_eql(api_v3_paths.version(shared_version_in_project.id).to_json)
|
|
.at_path("_embedded/elements/0/_links/self/href")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "DELETE /api/v3/versions/:id" do
|
|
let(:path) { api_v3_paths.version(version.id) }
|
|
let(:version) do
|
|
create(:version,
|
|
project:)
|
|
end
|
|
|
|
before do
|
|
login_as current_user
|
|
|
|
delete path
|
|
end
|
|
|
|
subject { last_response }
|
|
|
|
context "with required permissions" do
|
|
it "responds with HTTP No Content" do
|
|
expect(subject.status).to eq 204
|
|
end
|
|
|
|
it "deletes the version" do
|
|
expect(Version).not_to exist(version.id)
|
|
end
|
|
|
|
context "for a non-existent version" do
|
|
let(:path) { api_v3_paths.version 1337 }
|
|
|
|
it_behaves_like "not found"
|
|
end
|
|
end
|
|
|
|
context "with work packages attached to it" do
|
|
let(:version) do
|
|
create(:version,
|
|
project:).tap do |v|
|
|
create(:work_package,
|
|
project:,
|
|
version: v)
|
|
end
|
|
end
|
|
|
|
it "returns a 422" do
|
|
expect(subject.status)
|
|
.to be 422
|
|
end
|
|
|
|
it "does not delete the version" do
|
|
expect(Version).to exist(version.id)
|
|
end
|
|
end
|
|
|
|
context "without permission to see versions" do
|
|
let(:permissions) { [] }
|
|
|
|
it_behaves_like "not found"
|
|
end
|
|
|
|
context "without permission to delete versions" do
|
|
let(:permissions) { [:view_work_packages] }
|
|
|
|
it_behaves_like "unauthorized access"
|
|
|
|
it "does not delete the version" do
|
|
expect(Version).to exist(version.id)
|
|
end
|
|
end
|
|
end
|
|
end
|