update patterns contract and changes to the set attributes service

This commit is contained in:
Marcello Rocha
2025-06-17 17:37:00 +02:00
parent a525a9f5ed
commit eab36fad8a
5 changed files with 272 additions and 1 deletions
@@ -0,0 +1,57 @@
# 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 WorkPackageTypes
class UpdateSubjectPatternContract < BaseContract
attribute :patterns
validate :validate_subject_generation_pattern
private
def validate_subject_generation_pattern
blueprint = model.patterns.subject&.blueprint
return if blueprint.nil?
valid_tokens = flat_valid_token_list
invalid_tokens = blueprint.scan(Types::PatternResolver::TOKEN_REGEX)
.reduce([]) do |acc, match|
token = Types::Patterns::PatternToken.build(match).key
valid_tokens.include?(token) ? acc : acc << token
end
if invalid_tokens.any?
errors.add(:patterns, :invalid_tokens)
end
end
def flat_valid_token_list = Types::Patterns::TokenPropertyMapper.new.tokens_for_type(model).map(&:key)
end
end
@@ -30,10 +30,41 @@
module WorkPackageTypes
class SetAttributesService < ::BaseServices::SetAttributes
def initialize(user:, model:, contract_class:, contract_options: nil)
super
@valid_pattern = true
end
private
def set_attributes(params)
super(params.except(:copy_workflow_from))
permitted = params.except(:copy_workflow_from)
@valid_pattern = check_patterns(permitted)
if @valid_pattern
super(permitted)
else
super(permitted.except(:patterns))
end
end
def validate_and_result
success, errors = validate(model, user, options: {})
if @valid_pattern
ServiceResult.new(success:, errors:, result: model)
else
errors.add(:patterns, :is_invalid)
ServiceResult.failure(errors:, result: model)
end
end
def check_patterns(params)
return true if params.key?(:patterns) && params[:patterns].blank?
Types::Patterns::CollectionContract.new.call(params[:patterns]).success?
rescue ArgumentError
false
end
end
end
@@ -0,0 +1,86 @@
# 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"
module WorkPackageTypes
RSpec.describe UpdateSubjectPatternContract do
let(:model) { create(:type, :with_subject_pattern) }
let(:user) { create(:admin) }
subject(:contract) { described_class.new(model, user) }
context "when the user isn't admin" do
let(:user) { create(:user) }
it "the contract is invalid" do
expect(contract.validate).to be_falsey
end
it "adds and error to the contract" do
contract.validate
expect(contract.errors.details).to eq(base: [{ error: :error_unauthorized }])
end
end
describe "subject_pattern validation" do
let(:valid_pattern) { { subject: { blueprint: "{{author}}", enabled: true } } }
let(:invalid_pattern) { { subject: { blueprint: "{{vader_s_rubber_duck}}", enabled: true } } }
context "with no previous subject patterns" do
let(:model) { create(:type) }
it "is valid with a valid pattern" do
model.patterns = valid_pattern
expect(contract.validate).to be_truthy
end
it "is invalid if the pattern has bad tokens" do
model.patterns = invalid_pattern
expect(contract.validate).to be_falsey
end
it "adds an error if the pattern has bad tokens" do
model.patterns = invalid_pattern
contract.validate
expect(contract.errors.details).to eq(patterns: [{ error: :invalid_tokens }])
end
end
context "with a valid subject pattern" do
it "succeeds" do
model.patterns = valid_pattern
expect(model.validate).to be_truthy
end
end
end
end
end
+4
View File
@@ -58,6 +58,10 @@ FactoryBot.define do
end
end
trait :with_subject_pattern do
patterns { { subject: { blueprint: "{{author}} - {{status}}/{{type}} - {{id}}", enabled: true } } }
end
trait :default do
is_default { true }
end
@@ -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"
module WorkPackageTypes
RSpec.describe SetAttributesService do
let(:user) { create(:admin) }
let(:model) { create(:type, :with_subject_pattern) }
let(:params) { Hash.new }
subject(:service) { described_class.new(user:, model:, contract_class: UpdateSubjectPatternContract) }
context "when the pattern is malformed rubbish" do
let(:params) { { patterns: "vader_s_rubber_duck" } }
it "fails" do
result = service.perform(params)
expect(result).to be_failure
end
it "adds an error on the patterns atrribute" do
result = service.perform(params)
expect(result.errors.details).to eq(patterns: [{ error: :is_invalid }])
end
it "does not override the already existing value on the model" do
service.perform(params)
expect(model).not_to be_changed
end
end
context "when the pattern is invalid" do
let(:params) { { patterns: { subject: { blueprint: "{{author}}" } } } }
it "fails" do
result = service.perform(params)
expect(result).to be_failure
end
it "adds an error on the patterns attribute" do
result = service.perform(params)
expect(result.errors.details).to eq(patterns: [{ error: :is_invalid }])
end
it "does not override the already existing value on the model" do
service.perform(params)
expect(model).not_to be_changed
end
end
context "when the pattern is blank" do
let(:params) { { patterns: nil } }
it "succeeds" do
expect(service.perform(params)).to be_success
end
it "sets the patterns to an empty collection" do
service.perform(params)
expect(model.patterns).to eq(Types::Patterns::Collection.empty)
end
end
end
end