2025-05-05 09:29:55 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2019-08-06 09:02:47 +02:00
|
|
|
#-- copyright
|
2020-01-15 11:31:26 +01:00
|
|
|
# OpenProject is an open source project management software.
|
2024-07-30 13:42:36 +02:00
|
|
|
# Copyright (C) the OpenProject GmbH
|
2019-08-06 09:02:47 +02:00
|
|
|
#
|
|
|
|
|
# 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:
|
2021-01-13 17:47:45 +01:00
|
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
2019-08-06 09:02:47 +02:00
|
|
|
# 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.
|
|
|
|
|
#
|
2021-09-02 21:49:06 +02:00
|
|
|
# See COPYRIGHT and LICENSE files for more details.
|
2019-08-06 09:02:47 +02:00
|
|
|
#++
|
|
|
|
|
|
|
|
|
|
require "spec_helper"
|
2025-08-01 16:13:20 +02:00
|
|
|
require "contracts/shared/model_contract_shared_context"
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2023-05-31 12:15:15 +02:00
|
|
|
RSpec.shared_examples_for "project contract" do
|
2025-08-01 16:13:20 +02:00
|
|
|
include_context "ModelContract shared context"
|
|
|
|
|
|
2023-09-27 18:07:01 +02:00
|
|
|
let(:current_user) { build_stubbed(:user) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
mock_permissions_for(current_user) do |mock|
|
2023-10-16 08:38:35 +02:00
|
|
|
mock.allow_in_project(*project_permissions, project:)
|
|
|
|
|
mock.allow_globally(*global_permissions)
|
2019-08-07 15:16:00 +02:00
|
|
|
end
|
2025-08-07 17:38:31 +02:00
|
|
|
|
|
|
|
|
assignable_parents_scope = instance_double(ActiveRecord::Relation,
|
|
|
|
|
empty?: assignable_parents.empty?,
|
|
|
|
|
to_a: assignable_parents)
|
|
|
|
|
|
|
|
|
|
allow(Project)
|
|
|
|
|
.to receive(:assignable_parents)
|
|
|
|
|
.and_return(assignable_parents_scope)
|
|
|
|
|
|
|
|
|
|
allow(assignable_parents_scope)
|
|
|
|
|
.to receive(:exists?) do |id:|
|
|
|
|
|
assignable_parents.map(&:id).include?(id)
|
|
|
|
|
end
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
2023-09-27 18:07:01 +02:00
|
|
|
|
2023-10-13 12:31:46 +02:00
|
|
|
let(:project_permissions) { [] }
|
|
|
|
|
let(:global_permissions) { [] }
|
2019-08-06 09:02:47 +02:00
|
|
|
let(:project_name) { "Project name" }
|
|
|
|
|
let(:project_identifier) { "project_identifier" }
|
|
|
|
|
let(:project_description) { "Project description" }
|
2019-09-23 17:34:56 +02:00
|
|
|
let(:project_active) { true }
|
2019-08-06 09:02:47 +02:00
|
|
|
let(:project_public) { true }
|
2023-04-29 17:25:23 -05:00
|
|
|
let(:project_status_code) { "on_track" }
|
|
|
|
|
let(:project_status_explanation) { "some explanation" }
|
2025-08-01 16:28:40 +02:00
|
|
|
let(:project_workspace_type) { "project" }
|
2025-08-07 16:46:59 +02:00
|
|
|
let(:project_templated) { false }
|
2019-08-06 09:02:47 +02:00
|
|
|
let(:project_parent) do
|
2022-01-24 19:22:35 +01:00
|
|
|
build_stubbed(:project)
|
2019-09-12 16:13:07 +02:00
|
|
|
end
|
2025-08-07 17:38:31 +02:00
|
|
|
let(:assignable_parents) { [project_parent] }
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2019-08-06 09:02:47 +02:00
|
|
|
|
|
|
|
|
context "if the name is nil" do
|
|
|
|
|
let(:project_name) { nil }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", name: %i(blank)
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "if the description is nil" do
|
|
|
|
|
let(:project_description) { nil }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "if the parent is nil" do
|
|
|
|
|
let(:project_parent) { nil }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
|
2019-09-12 16:13:07 +02:00
|
|
|
context "if the parent is not in the set of assignable_parents" do
|
2025-08-07 17:38:31 +02:00
|
|
|
let(:assignable_parents) { [] }
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2025-11-17 16:47:48 +01:00
|
|
|
it_behaves_like "contract is invalid", parent: %i(invalid)
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
|
2019-09-26 09:23:34 +02:00
|
|
|
context "if active is nil" do
|
2019-09-23 17:34:56 +02:00
|
|
|
let(:project_active) { nil }
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", active: %i(blank)
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
|
2023-04-29 17:25:23 -05:00
|
|
|
context "if status code is nil" do
|
|
|
|
|
let(:project_status_code) { nil }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2023-04-29 17:25:23 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "if status explanation is nil" do
|
|
|
|
|
let(:project_status_explanation) { nil }
|
2019-09-26 09:23:34 +02:00
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2019-09-26 09:23:34 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "if status code is invalid" do
|
|
|
|
|
before do
|
2023-04-29 17:25:23 -05:00
|
|
|
# Hack in order to handle setting an Enum value without raising an
|
|
|
|
|
# ArgumentError and letting the Contract perform the validation.
|
|
|
|
|
#
|
|
|
|
|
# This is the behavior that would be expected to be performed by
|
|
|
|
|
# the SetAttributesService at that layer of the flow.
|
|
|
|
|
bogus_project_status_code = "bogus"
|
|
|
|
|
code_attributes = project.instance_variable_get(:@attributes)["status_code"]
|
|
|
|
|
code_attributes.instance_variable_set(:@value_before_type_cast, bogus_project_status_code)
|
|
|
|
|
code_attributes.instance_variable_set(:@value, bogus_project_status_code)
|
2019-09-26 09:23:34 +02:00
|
|
|
end
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", status: %i(inclusion)
|
2019-09-26 09:23:34 +02:00
|
|
|
end
|
|
|
|
|
|
2021-06-08 21:26:20 +02:00
|
|
|
context "when the identifier consists of only letters" do
|
|
|
|
|
let(:project_identifier) { "abc" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of letters followed by numbers" do
|
|
|
|
|
let(:project_identifier) { "abc12" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of letters followed by numbers with a hyphen in between" do
|
|
|
|
|
let(:project_identifier) { "abc-12" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
2022-05-31 11:55:27 +02:00
|
|
|
|
2021-06-08 21:26:20 +02:00
|
|
|
context "when the identifier consists of letters followed by numbers with an underscore in between" do
|
|
|
|
|
let(:project_identifier) { "abc_12" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of numbers followed by letters with a hyphen in between" do
|
|
|
|
|
let(:project_identifier) { "12-abc" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of numbers followed by letters with an underscore in between" do
|
|
|
|
|
let(:project_identifier) { "12_abc" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is valid"
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of only numbers" do
|
|
|
|
|
let(:project_identifier) { "12" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", identifier: %i(invalid)
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the identifier consists of a reserved word" do
|
|
|
|
|
let(:project_identifier) { "new" }
|
|
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", identifier: %i(exclusion)
|
2021-06-08 21:26:20 +02:00
|
|
|
end
|
|
|
|
|
|
2025-08-07 16:46:59 +02:00
|
|
|
context "when changing templated as an admin" do
|
|
|
|
|
let(:current_user) { build_stubbed(:admin) }
|
|
|
|
|
let(:project_templated) { true }
|
|
|
|
|
|
|
|
|
|
it_behaves_like "contract is valid"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when changing templated as a user" do
|
|
|
|
|
let(:project_templated) { true }
|
|
|
|
|
|
|
|
|
|
it_behaves_like "contract is invalid", templated: %i(error_unauthorized)
|
|
|
|
|
end
|
|
|
|
|
|
2019-08-07 15:16:00 +02:00
|
|
|
context "if the user lacks permission" do
|
2023-10-13 12:31:46 +02:00
|
|
|
let(:global_permissions) { [] }
|
|
|
|
|
let(:project_permissions) { [] }
|
2019-08-07 15:16:00 +02:00
|
|
|
|
2025-08-01 16:13:20 +02:00
|
|
|
it_behaves_like "contract is invalid", base: %i(error_unauthorized)
|
2019-08-07 15:16:00 +02:00
|
|
|
end
|
|
|
|
|
|
2025-08-07 16:49:50 +02:00
|
|
|
describe "assignable_parents" do
|
|
|
|
|
before do
|
|
|
|
|
assignable_parents
|
|
|
|
|
end
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2025-08-07 17:38:31 +02:00
|
|
|
it "returns the projects Project.assignable_parents returns" do
|
|
|
|
|
expect(contract.assignable_parents.to_a)
|
2025-08-07 16:49:50 +02:00
|
|
|
.to eql assignable_parents
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
2025-08-07 16:49:50 +02:00
|
|
|
end
|
2019-08-06 09:02:47 +02:00
|
|
|
|
2025-08-07 16:49:50 +02:00
|
|
|
describe "assignable_custom_field_values" do
|
2019-09-09 14:55:50 +02:00
|
|
|
context "for a list custom field" do
|
2022-01-24 19:22:35 +01:00
|
|
|
let(:custom_field) { build_stubbed(:list_project_custom_field) }
|
2019-08-06 09:02:47 +02:00
|
|
|
|
|
|
|
|
it "is the list of custom field values" do
|
|
|
|
|
expect(subject.assignable_custom_field_values(custom_field))
|
|
|
|
|
.to eql custom_field.possible_values
|
|
|
|
|
end
|
2019-09-09 14:55:50 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "for a version custom field" do
|
2022-01-24 19:22:35 +01:00
|
|
|
let(:custom_field) { build_stubbed(:version_project_custom_field) }
|
2019-09-09 14:55:50 +02:00
|
|
|
let(:versions) { double("versions") }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
allow(project)
|
|
|
|
|
.to receive(:assignable_versions)
|
|
|
|
|
.and_return(versions)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is the list of versions for the project" do
|
|
|
|
|
expect(subject.assignable_custom_field_values(custom_field))
|
|
|
|
|
.to eql versions
|
|
|
|
|
end
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "available_custom_fields" do
|
2024-08-05 17:53:09 +03:00
|
|
|
let(:visible_custom_field) { build_stubbed(:integer_project_custom_field, admin_only: false) }
|
|
|
|
|
let(:invisible_custom_field) { build_stubbed(:integer_project_custom_field, admin_only: true) }
|
2019-08-06 09:02:47 +02:00
|
|
|
|
|
|
|
|
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)
|
2023-08-28 17:43:08 +02:00
|
|
|
.to contain_exactly(visible_custom_field, invisible_custom_field)
|
2019-08-06 09:02:47 +02:00
|
|
|
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)
|
2023-08-28 17:43:08 +02:00
|
|
|
.to contain_exactly(visible_custom_field)
|
2019-08-06 09:02:47 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|