diff --git a/app/contracts/labels/base_contract.rb b/app/contracts/labels/base_contract.rb new file mode 100644 index 000000000000..df72474fd304 --- /dev/null +++ b/app/contracts/labels/base_contract.rb @@ -0,0 +1,35 @@ +# 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. +#++ + +module Labels + class BaseContract < ::ModelContract + attribute :name + end +end diff --git a/app/contracts/labels/create_contract.rb b/app/contracts/labels/create_contract.rb new file mode 100644 index 000000000000..138068ae86db --- /dev/null +++ b/app/contracts/labels/create_contract.rb @@ -0,0 +1,43 @@ +# 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. +#++ + +module Labels + class CreateContract < BaseContract + validate :user_allowed_to_create + + private + + def user_allowed_to_create + unless user.active? && (user.admin? || user.allowed_in_any_project?(:edit_work_packages)) + errors.add :base, :error_unauthorized + end + end + end +end diff --git a/app/contracts/labels/delete_contract.rb b/app/contracts/labels/delete_contract.rb new file mode 100644 index 000000000000..4b0eb5804c2d --- /dev/null +++ b/app/contracts/labels/delete_contract.rb @@ -0,0 +1,35 @@ +# 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. +#++ + +module Labels + class DeleteContract < ::DeleteContract + delete_permission :admin + end +end diff --git a/app/contracts/labels/update_contract.rb b/app/contracts/labels/update_contract.rb new file mode 100644 index 000000000000..d2a92df2eb77 --- /dev/null +++ b/app/contracts/labels/update_contract.rb @@ -0,0 +1,35 @@ +# 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. +#++ + +module Labels + class UpdateContract < BaseContract + include RequiresAdminGuard + end +end diff --git a/app/models/label.rb b/app/models/label.rb index c6c2b8fb2809..763f5b5d9d29 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -33,11 +33,11 @@ class Label < ApplicationRecord has_many :labelings, dependent: :delete_all scope :with_usage_count, -> { - left_joins(:labelings) - .select("labels.*, COUNT(labelings.id) AS usage_count") - .group(:id) + select("labels.*, (SELECT COUNT(*) FROM labelings WHERE labelings.label_id = labels.id) AS usage_count") } + normalizes :name, with: -> { it.squish } + validates :name, presence: true, uniqueness: { case_sensitive: false }, diff --git a/app/models/queries/labels.rb b/app/models/queries/labels.rb new file mode 100644 index 000000000000..2ac693a3ea59 --- /dev/null +++ b/app/models/queries/labels.rb @@ -0,0 +1,35 @@ +# 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. +#++ + +module Queries::Labels + ::Queries::Register.register(LabelQuery) do + filter Filters::NameFilter + end +end diff --git a/app/models/queries/labels/filters/label_filter.rb b/app/models/queries/labels/filters/label_filter.rb new file mode 100644 index 000000000000..7a10433f6d7c --- /dev/null +++ b/app/models/queries/labels/filters/label_filter.rb @@ -0,0 +1,33 @@ +# 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. +#++ + +class Queries::Labels::Filters::LabelFilter < Queries::Filters::Base + self.model = Label +end diff --git a/app/models/queries/labels/filters/name_filter.rb b/app/models/queries/labels/filters/name_filter.rb new file mode 100644 index 000000000000..12e1dfab4131 --- /dev/null +++ b/app/models/queries/labels/filters/name_filter.rb @@ -0,0 +1,59 @@ +# 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. +#++ + +class Queries::Labels::Filters::NameFilter < Queries::Labels::Filters::LabelFilter + def self.key + :name + end + + def type + :string + end + + def human_name + Label.human_attribute_name(name) + end + + def available_operators + [::Queries::Operators::Contains, ::Queries::Operators::Everywhere, ::Queries::Operators::NotContains] + end + + def where + escaped = ActiveRecord::Base.sanitize_sql_like(values.first) + case operator + when "~", "**" + ["labels.name ILIKE :q", { q: "%#{escaped}%" }] + when "!~" + ["labels.name NOT ILIKE :q", { q: "%#{escaped}%" }] + else + raise "Unsupported operator #{operator}" + end + end +end diff --git a/app/models/queries/labels/label_query.rb b/app/models/queries/labels/label_query.rb new file mode 100644 index 000000000000..44ec09a0a67f --- /dev/null +++ b/app/models/queries/labels/label_query.rb @@ -0,0 +1,42 @@ +# 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. +#++ + +class Queries::Labels::LabelQuery + include Queries::BaseQuery + include Queries::UnpersistedQuery + + def self.model + Label + end + + def default_scope + Label.with_usage_count.order(Label.arel_table[:name].lower.asc) + end +end diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb new file mode 100644 index 000000000000..61b4bee4b80d --- /dev/null +++ b/app/services/labels/create_service.rb @@ -0,0 +1,34 @@ +# 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. +#++ + +module Labels + class CreateService < ::BaseServices::Create + end +end diff --git a/app/services/labels/delete_service.rb b/app/services/labels/delete_service.rb new file mode 100644 index 000000000000..3efeeb5600c9 --- /dev/null +++ b/app/services/labels/delete_service.rb @@ -0,0 +1,34 @@ +# 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. +#++ + +module Labels + class DeleteService < ::BaseServices::Delete + end +end diff --git a/app/services/labels/set_attributes_service.rb b/app/services/labels/set_attributes_service.rb new file mode 100644 index 000000000000..82c923683c04 --- /dev/null +++ b/app/services/labels/set_attributes_service.rb @@ -0,0 +1,41 @@ +# 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. +#++ + +module Labels + class SetAttributesService < ::BaseServices::SetAttributes + private + + def set_default_attributes(_params) + model.change_by_system do + model.author = user + end + end + end +end diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb new file mode 100644 index 000000000000..c1c55adede2a --- /dev/null +++ b/app/services/labels/update_service.rb @@ -0,0 +1,34 @@ +# 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. +#++ + +module Labels + class UpdateService < ::BaseServices::Update + end +end diff --git a/spec/contracts/labels/create_contract_spec.rb b/spec/contracts/labels/create_contract_spec.rb new file mode 100644 index 000000000000..ec9e2e9160b0 --- /dev/null +++ b/spec/contracts/labels/create_contract_spec.rb @@ -0,0 +1,66 @@ +# 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 "contracts/shared/model_contract_shared_context" +require_relative "shared_contract_examples" + +RSpec.describe Labels::CreateContract do + include_context "ModelContract shared context" + + shared_let(:project) { create(:project) } + + let(:label) { build(:label) } + let(:contract) { described_class.new(label, current_user) } + + it_behaves_like "label contract" do + let(:current_user) do + create(:user, member_with_permissions: { project => %i[edit_work_packages] }) + end + end + + context "when admin" do + let(:current_user) { create(:admin) } + + it_behaves_like "contract is valid" + end + + context "when member without edit_work_packages permission" do + let(:current_user) do + create(:user, member_with_permissions: { project => %i[view_work_packages] }) + end + + it_behaves_like "contract user is unauthorized" + end + + include_examples "contract reuses the model errors" do + let(:current_user) { create(:admin) } + end +end diff --git a/spec/contracts/labels/delete_contract_spec.rb b/spec/contracts/labels/delete_contract_spec.rb new file mode 100644 index 000000000000..8c2a50b80f42 --- /dev/null +++ b/spec/contracts/labels/delete_contract_spec.rb @@ -0,0 +1,45 @@ +# 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 "contracts/shared/model_contract_shared_context" + +RSpec.describe Labels::DeleteContract do + include_context "ModelContract shared context" + + let(:label) { create(:label) } + let(:contract) { described_class.new(label, current_user) } + + it_behaves_like "contract is valid for active admins and invalid for regular users" + + include_examples "contract reuses the model errors" do + let(:current_user) { build_stubbed(:admin) } + end +end diff --git a/spec/contracts/labels/shared_contract_examples.rb b/spec/contracts/labels/shared_contract_examples.rb new file mode 100644 index 000000000000..06ef404e3d14 --- /dev/null +++ b/spec/contracts/labels/shared_contract_examples.rb @@ -0,0 +1,61 @@ +# 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" + +RSpec.shared_examples_for "label contract" do + let(:locked_user) { create(:admin, status: User.statuses[:locked]) } + + it_behaves_like "contract is valid" + + context "when name is blank" do + before { label.name = "" } + + it_behaves_like "contract is invalid", name: :blank + end + + context "when name is too long" do + before { label.name = "a" * 256 } + + it_behaves_like "contract is invalid", name: :too_long + end + + context "when name is already taken" do + before { create(:label, name: label.name.upcase) } + + it_behaves_like "contract is invalid", name: :taken + end + + context "when the acting user is locked" do + let(:contract) { described_class.new(label, locked_user) } + + it_behaves_like "contract user is unauthorized" + end +end diff --git a/spec/contracts/labels/update_contract_spec.rb b/spec/contracts/labels/update_contract_spec.rb new file mode 100644 index 000000000000..2ae1f9e73856 --- /dev/null +++ b/spec/contracts/labels/update_contract_spec.rb @@ -0,0 +1,54 @@ +# 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 "contracts/shared/model_contract_shared_context" +require_relative "shared_contract_examples" + +RSpec.describe Labels::UpdateContract do + include_context "ModelContract shared context" + + let(:label) { build_stubbed(:label) } + let(:contract) { described_class.new(label, current_user) } + + it_behaves_like "label contract" do + let(:current_user) { create(:admin) } + end + + context "when non-admin" do + let(:current_user) { create(:user) } + + it_behaves_like "contract user is unauthorized" + end + + include_examples "contract reuses the model errors" do + let(:current_user) { create(:admin) } + end +end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index a9fbb5351fbf..382c4e6a38ed 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -48,6 +48,12 @@ end end + describe "normalizations" do + subject { build(:label) } + + it { is_expected.to normalize(:name).from(" Machine Learning ").to("Machine Learning") } + end + describe ".with_usage_count" do it "counts the labelings of each label, including unused ones" do used = create(:label) @@ -58,6 +64,14 @@ expect(counts).to eq(used.id => 2, unused.id => 0) end + + it "keeps a scalar total when paginated" do + create_list(:label, 2) + create(:labeling, label: described_class.first) + + expect(described_class.with_usage_count.paginate(page: 1, per_page: 1).total_entries) + .to eq(described_class.count) + end end describe "#destroy" do diff --git a/spec/models/queries/labels/filters/name_filter_spec.rb b/spec/models/queries/labels/filters/name_filter_spec.rb new file mode 100644 index 000000000000..136f7c967974 --- /dev/null +++ b/spec/models/queries/labels/filters/name_filter_spec.rb @@ -0,0 +1,83 @@ +# 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" + +RSpec.describe Queries::Labels::Filters::NameFilter do + include_context "filter tests" + let(:values) { ["app"] } + let(:model) { Label } + + it_behaves_like "basic query filter" do + let(:class_key) { :name } + let(:human_name) { Label.human_attribute_name(:name) } + let(:type) { :string } + let(:model) { Label } + + describe "#available_operators" do + it "supports ~, ** and !~" do + expect(instance.available_operators) + .to eql [Queries::Operators::Contains, Queries::Operators::Everywhere, Queries::Operators::NotContains] + end + end + end + + describe "#apply_to" do + context 'for "~"' do + let(:operator) { "~" } + + it "is the same as handwriting the query" do + expected = model.where("labels.name ILIKE '%app%'") + + expect(instance.apply_to(model).to_sql).to eql expected.to_sql + end + end + + context 'for "**"' do + let(:operator) { "**" } + + it "is the same as handwriting the query" do + expected = model.where("labels.name ILIKE '%app%'") + + expect(instance.apply_to(model).to_sql).to eql expected.to_sql + end + end + + context 'for "!~"' do + let(:operator) { "!~" } + + it "is the same as handwriting the query" do + expected = model.where("labels.name NOT ILIKE '%app%'") + + expect(instance.apply_to(model).to_sql).to eql expected.to_sql + end + end + end +end diff --git a/spec/models/queries/labels/label_query_spec.rb b/spec/models/queries/labels/label_query_spec.rb new file mode 100644 index 000000000000..07b4e334543b --- /dev/null +++ b/spec/models/queries/labels/label_query_spec.rb @@ -0,0 +1,82 @@ +# 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" + +RSpec.describe Queries::Labels::LabelQuery do + shared_let(:admin) { create(:admin) } + + shared_let(:banana) { create(:label, name: "banana") } + shared_let(:apple) { create(:label, name: "Apple") } + shared_let(:cherry) { create(:label, name: "cherry") } + + let(:instance) { described_class.new(user: admin) } + + describe "#results" do + it "orders labels case-insensitively by name" do + expect(instance.results.to_a).to eq([apple, banana, cherry]) + end + + it "exposes the usage count of each label" do + create(:labeling, label: apple) + + counts = instance.results.index_by(&:id).transform_values(&:usage_count) + + expect(counts).to eq(apple.id => 1, banana.id => 0, cherry.id => 0) + end + + it "paginates with a correct scalar total" do + expect(instance.results.paginate(page: 1, per_page: 1).total_entries).to eq(3) + end + end + + describe "via ParamsToQueryService" do + it "applies the name filter parsed from JSON params" do + params = ActionController::Parameters.new( + filters: [{ name: { operator: "~", values: ["app"] } }].to_json + ) + + query = ParamsToQueryService.new(Label, admin, query_class: described_class).call(params) + + expect(query.results.to_a).to eq([apple]) + end + + it "rejects the = operator without raising on results" do + params = ActionController::Parameters.new( + filters: [{ name: { operator: "=", values: ["x"] } }].to_json + ) + + query = ParamsToQueryService.new(Label, admin, query_class: described_class).call(params) + + expect(query.valid?).to be false + expect { query.results.to_a }.not_to raise_error + end + end +end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb new file mode 100644 index 000000000000..4de08743e4fd --- /dev/null +++ b/spec/services/labels/create_service_spec.rb @@ -0,0 +1,93 @@ +# 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 "services/base_services/behaves_like_create_service" + +RSpec.describe Labels::CreateService, type: :model do + it_behaves_like "BaseServices create service" do + let(:call_attributes) { { name: "Some name" } } + + context "with a real service call" do + let(:stub_model_instance) { false } + let(:user) { create(:admin) } + let(:call_attributes) { { name: "Bug" } } + + it "creates the label, setting the author to the calling user" do + expect(subject).to be_success + + label = subject.result + expect(label).to be_persisted + expect(label.author).to eq(user) + expect(label.name).to eq("Bug") + end + + it "fails when the name is already taken, case-insensitively" do + create(:label, name: "Bug") + + result = described_class.new(user:).call(name: "BUG") + + expect(result).to be_failure + expect(result.errors.symbols_for(:name)).to include(:taken) + end + + it "fails when the name is already taken but for surrounding whitespace" do + create(:label, name: "Machine Learning") + + result = described_class.new(user:).call(name: " Machine Learning ") + + expect(result).to be_failure + expect(result.errors.symbols_for(:name)).to include(:taken) + end + + context "with a user lacking edit_work_packages in any project" do + let(:user) { create(:user) } + + it "is unauthorized" do + expect(subject).to be_failure + expect(subject.errors.symbols_for(:base)).to include(:error_unauthorized) + end + end + + context "with a member holding edit_work_packages in a project" do + let(:project) { create(:project) } + let(:user) { create(:user, member_with_permissions: { project => %i[edit_work_packages] }) } + + it "creates the label, setting the author to the calling user" do + expect(subject).to be_success + + label = subject.result + expect(label).to be_persisted + expect(label.author).to eq(user) + end + end + end + end +end diff --git a/spec/services/labels/delete_service_spec.rb b/spec/services/labels/delete_service_spec.rb new file mode 100644 index 000000000000..646ad66e369e --- /dev/null +++ b/spec/services/labels/delete_service_spec.rb @@ -0,0 +1,65 @@ +# 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 "services/base_services/behaves_like_delete_service" + +RSpec.describe Labels::DeleteService, type: :model do + it_behaves_like "BaseServices delete service" + + describe "with a real service call" do + shared_let(:admin) { create(:admin) } + + let(:label) { create(:label) } + let(:instance) { described_class.new(user: admin, model: label) } + + it "removes the label and its labelings" do + labeling = create(:labeling, label:) + + result = instance.call + + expect(result).to be_success + expect(Label.where(id: label.id)).not_to exist + expect(Labeling.where(id: labeling.id)).not_to exist + end + + context "with a non-admin user" do + let(:instance) { described_class.new(user: create(:user), model: label) } + + it "is unauthorized and leaves the label intact" do + result = instance.call + + expect(result).to be_failure + expect(result.errors.symbols_for(:base)).to include(:error_unauthorized) + expect(Label.where(id: label.id)).to exist + end + end + end +end diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb new file mode 100644 index 000000000000..7d1836f7ce39 --- /dev/null +++ b/spec/services/labels/update_service_spec.rb @@ -0,0 +1,76 @@ +# 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 "services/base_services/behaves_like_update_service" + +RSpec.describe Labels::UpdateService, type: :model do + it_behaves_like "BaseServices update service" do + let(:call_attributes) { { name: "Some name" } } + end + + describe "with a real service call" do + shared_let(:admin) { create(:admin) } + + let(:label) { create(:label, name: "Bug") } + let(:instance) { described_class.new(user: admin, model: label) } + + it "renames the label" do + result = instance.call(name: "Defect") + + expect(result).to be_success + expect(label.reload.name).to eq("Defect") + end + + context "when the new name is already taken in a different case" do + before { create(:label, name: "defect") } + + it "fails with a taken error and does not persist the change" do + result = instance.call(name: "Defect") + + expect(result).to be_failure + expect(result.errors.symbols_for(:name)).to include(:taken) + expect(label.reload.name).to eq("Bug") + end + end + + context "with a non-admin user" do + let(:instance) { described_class.new(user: create(:user), model: label) } + + it "is unauthorized" do + result = instance.call(name: "Defect") + + expect(result).to be_failure + expect(result.errors.symbols_for(:base)).to include(:error_unauthorized) + expect(label.reload.name).to eq("Bug") + end + end + end +end