[36238] Extract and fix user references in other objects (#9007)
* Move replacing invalid references into separate job for principals * Write migration to remove existing invalid custom values and responsible * Fix other specs * Fix other specs * rewrite replacing user in records * consolidate principal deletion * include placeholder users in spec Co-authored-by: ulferts <jens.ulferts@googlemail.com>pull/9015/head
parent
36e229a461
commit
f4dfd6c6c6
@ -1,33 +0,0 @@ |
||||
module Journals |
||||
class UserReferenceUpdateService |
||||
attr_accessor :original_user |
||||
|
||||
def initialize(original_user) |
||||
self.original_user = original_user |
||||
end |
||||
|
||||
def call(substitute_user) |
||||
journal_classes.each do |klass| |
||||
foreign_keys.each do |foreign_key| |
||||
if klass.column_names.include? foreign_key |
||||
klass |
||||
.where(foreign_key => original_user.id) |
||||
.update_all(foreign_key => substitute_user.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
ServiceResult.new success: true |
||||
end |
||||
|
||||
private |
||||
|
||||
def journal_classes |
||||
[Journal] + Journal::BaseJournal.subclasses |
||||
end |
||||
|
||||
def foreign_keys |
||||
%w[author_id user_id assigned_to_id responsible_id] |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,126 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
# Rewrites references to a principal from one principal to the other. |
||||
# No data is to be removed. |
||||
module Principals |
||||
class ReplaceReferencesService |
||||
def call(from:, to:) |
||||
rewrite_active_models(from, to) |
||||
rewrite_custom_value(from, to) |
||||
rewrite_default_journals(from, to) |
||||
rewrite_customizable_journals(from, to) |
||||
|
||||
ServiceResult.new success: true |
||||
end |
||||
|
||||
private |
||||
|
||||
# rubocop:disable Rails/SkipsModelValidations |
||||
def rewrite_active_models(from, to) |
||||
rewrite_author(from, to) |
||||
rewrite_user(from, to) |
||||
rewrite_assigned_to(from, to) |
||||
rewrite_responsible(from, to) |
||||
end |
||||
|
||||
def rewrite_custom_value(from, to) |
||||
CustomValue |
||||
.where(custom_field_id: CustomField.where(field_format: 'user')) |
||||
.where(value: from.id.to_s) |
||||
.update_all(value: to.id.to_s) |
||||
end |
||||
|
||||
def rewrite_default_journals(from, to) |
||||
journal_classes.each do |klass| |
||||
foreign_keys.each do |foreign_key| |
||||
if klass.column_names.include? foreign_key |
||||
klass |
||||
.where(foreign_key => from.id) |
||||
.update_all(foreign_key => to.id) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
def rewrite_customizable_journals(from, to) |
||||
Journal::CustomizableJournal |
||||
.joins(:custom_field) |
||||
.where(custom_fields: { field_format: 'user' }) |
||||
.where(value: from.id.to_s) |
||||
.update_all(value: to.id.to_s) |
||||
end |
||||
|
||||
def rewrite_author(from, to) |
||||
[WorkPackage, |
||||
Attachment, |
||||
WikiContent, |
||||
News, |
||||
Comment, |
||||
Message, |
||||
Budget, |
||||
MeetingAgenda, |
||||
MeetingMinutes].each do |klass| |
||||
klass.where(author_id: from.id).update_all(author_id: to.id) |
||||
end |
||||
end |
||||
|
||||
def rewrite_user(from, to) |
||||
[TimeEntry, |
||||
::Query, |
||||
Changeset, |
||||
CostQuery, |
||||
MeetingParticipant].each do |klass| |
||||
klass.where(user_id: from.id).update_all(user_id: to.id) |
||||
end |
||||
end |
||||
|
||||
def rewrite_assigned_to(from, to) |
||||
[WorkPackage].each do |klass| |
||||
klass.where(assigned_to_id: from.id).update_all(assigned_to_id: to.id) |
||||
end |
||||
end |
||||
|
||||
def rewrite_responsible(from, to) |
||||
[WorkPackage].each do |klass| |
||||
klass.where(responsible_id: from.id).update_all(responsible_id: to.id) |
||||
end |
||||
end |
||||
# rubocop:enable Rails/SkipsModelValidations |
||||
|
||||
def journal_classes |
||||
[Journal] + Journal::BaseJournal.subclasses |
||||
end |
||||
|
||||
def foreign_keys |
||||
%w[author_id user_id assigned_to_id responsible_id] |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,85 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class Principals::DeleteJob < ApplicationJob |
||||
queue_with_priority :low |
||||
|
||||
def perform(principal) |
||||
Principal.transaction do |
||||
delete_associated(principal) |
||||
replace_references(principal) |
||||
update_cost_queries(principal) |
||||
|
||||
principal.destroy |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def replace_references(principal) |
||||
Principals::ReplaceReferencesService |
||||
.new |
||||
.call(from: principal, to: DeletedUser.first) |
||||
.tap do |call| |
||||
raise ActiveRecord::Rollback if call.failure? |
||||
end |
||||
end |
||||
|
||||
def delete_associated(principal) |
||||
delete_private_queries(principal) |
||||
end |
||||
|
||||
def delete_private_queries(principal) |
||||
::Query.where(user_id: principal.id, is_public: false).delete_all |
||||
CostQuery.where(user_id: principal.id, is_public: false).delete_all |
||||
end |
||||
|
||||
# rubocop:disable Rails/SkipsModelValidations |
||||
def update_cost_queries(principal) |
||||
CostQuery.in_batches.each_record do |query| |
||||
serialized = query.serialized |
||||
|
||||
serialized[:filters] = serialized[:filters].map do |name, options| |
||||
remove_cost_query_values(name, options, principal) |
||||
end.compact |
||||
|
||||
CostQuery.where(id: query.id).update_all(serialized: serialized) |
||||
end |
||||
end |
||||
# rubocop:enable Rails/SkipsModelValidations |
||||
|
||||
def remove_cost_query_values(name, options, principal) |
||||
options[:values].delete(principal.id.to_s) if %w[UserId AuthorId AssignedToId ResponsibleId].include?(name) |
||||
|
||||
if options[:values].nil? || options[:values].any? |
||||
[name, options] |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,22 @@ |
||||
class ReplaceInvalidPrincipalReferences < ActiveRecord::Migration[6.1] |
||||
def up |
||||
DeletedUser.reset_column_information |
||||
deleted_user_id = DeletedUser.first.id |
||||
|
||||
say "Replacing invalid custom value user references" |
||||
CustomValue |
||||
.joins(:custom_field) |
||||
.where("#{CustomField.table_name}.field_format" => 'user') |
||||
.where("value NOT IN (SELECT id::text FROM users)") |
||||
.update_all(value: deleted_user_id) |
||||
|
||||
say "Replacing invalid responsible user references in work packages" |
||||
WorkPackage |
||||
.where("responsible_id NOT IN (SELECT id FROM users)") |
||||
.update_all(responsible_id: deleted_user_id) |
||||
end |
||||
|
||||
def down |
||||
# Nothing to do, as only invalid data is fixed |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_time_entry_journal, class: Journal::TimeEntryJournal do |
||||
end |
||||
end |
@ -1,208 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require File.dirname(__FILE__) + '/../spec_helper' |
||||
|
||||
describe User, '#destroy', type: :model do |
||||
let(:user) { FactoryBot.create(:user) } |
||||
let(:user2) { FactoryBot.create(:user) } |
||||
let(:substitute_user) { DeletedUser.first } |
||||
let(:project) { FactoryBot.create(:valid_project) } |
||||
|
||||
before do |
||||
user |
||||
user2 |
||||
end |
||||
|
||||
after do |
||||
User.current = nil |
||||
end |
||||
|
||||
shared_examples_for 'costs updated journalized associated object' do |
||||
before do |
||||
User.current = user2 |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
User.current = user # in order to have the content journal created by the user |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by_id(associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(substitute_user) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(user2) } |
||||
it 'should update first journal details' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details["#{association}_id".to_sym].last).to eq(user2.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(substitute_user) } |
||||
it 'should update second journal details' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details["#{association}_id".to_sym].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'costs created journalized associated object' do |
||||
before do |
||||
User.current = user # in order to have the content journal created by the user |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
User.current = user2 |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by_id(associated_instance.id)).to eq(associated_instance) } |
||||
it 'should keep the current user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(user2) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(substitute_user) } |
||||
it 'should update the first journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details["#{association}_id".to_sym].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(user2) } |
||||
it 'should update the last journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details["#{association}_id".to_sym].first).to eq(substitute_user.id) |
||||
expect(associated_instance.journals.last.details["#{association}_id".to_sym].last).to eq(user2.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user updated a cost object' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) { FactoryBot.build(:budget) } |
||||
let(:associated_class) { Budget } |
||||
|
||||
it_should_behave_like 'costs updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user created a cost object' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) { FactoryBot.build(:budget) } |
||||
let(:associated_class) { Budget } |
||||
|
||||
it_should_behave_like 'costs created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has a labor_budget_item associated' do |
||||
let(:item) { FactoryBot.build(:labor_budget_item, user: user) } |
||||
|
||||
before do |
||||
item.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(LaborBudgetItem.find_by_id(item.id)).to eq(item) } |
||||
it { expect(item.user_id).to eq(user.id) } |
||||
end |
||||
|
||||
describe 'WHEN the user has a cost entry' do |
||||
let(:work_package) { FactoryBot.create(:work_package) } |
||||
let(:entry) do |
||||
FactoryBot.create(:cost_entry, user: user, |
||||
project: work_package.project, |
||||
units: 100.0, |
||||
spent_on: Date.today, |
||||
work_package: work_package, |
||||
comments: '') |
||||
end |
||||
|
||||
before do |
||||
FactoryBot.create(:member, project: work_package.project, |
||||
user: user, |
||||
roles: [FactoryBot.build(:role)]) |
||||
entry |
||||
|
||||
user.destroy |
||||
|
||||
entry.reload |
||||
end |
||||
|
||||
it { expect(entry.user_id).to eq(user.id) } |
||||
end |
||||
|
||||
describe 'WHEN the user is assigned an hourly rate' do |
||||
let(:hourly_rate) do |
||||
FactoryBot.build(:hourly_rate, user: user, |
||||
project: project) |
||||
end |
||||
|
||||
before do |
||||
hourly_rate.save! |
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(HourlyRate.find_by_id(hourly_rate.id)).to eq(hourly_rate) } |
||||
it { expect(hourly_rate.reload.user_id).to eq(user.id) } |
||||
end |
||||
|
||||
describe 'WHEN the user is assigned a default hourly rate' do |
||||
let(:default_hourly_rate) do |
||||
FactoryBot.build(:default_hourly_rate, user: user, |
||||
project: project) |
||||
end |
||||
|
||||
before do |
||||
default_hourly_rate.save! |
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(DefaultHourlyRate.find_by_id(default_hourly_rate.id)).to eq(default_hourly_rate) } |
||||
it { expect(default_hourly_rate.reload.user_id).to eq(user.id) } |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_document_journal, class: Journal::DocumentJournal do |
||||
end |
||||
end |
@ -1,207 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require File.dirname(__FILE__) + '/../spec_helper' |
||||
|
||||
describe User, '#destroy', type: :model do |
||||
let!(:user) { FactoryBot.create(:user) } |
||||
let!(:user2) { FactoryBot.create(:user) } |
||||
let(:substitute_user) { DeletedUser.first } |
||||
let(:project) do |
||||
FactoryBot.create(:valid_project) |
||||
end |
||||
|
||||
let(:meeting) do |
||||
FactoryBot.create(:meeting, |
||||
project: project, |
||||
author: user2) |
||||
end |
||||
let(:participant) do |
||||
FactoryBot.create(:meeting_participant, |
||||
user: user, |
||||
meeting: meeting, |
||||
invited: true, |
||||
attended: true) |
||||
end |
||||
|
||||
shared_examples_for 'updated journalized associated object' do |
||||
before do |
||||
allow(User).to receive(:current).and_return(user2) |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return(user) # in order to have the content journal created by the user |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by_id(associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(substitute_user) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(user2) } |
||||
it 'should update first journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details[(association.to_s + '_id').to_sym].last).to eq(user2.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(substitute_user) } |
||||
it 'should update second journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details[(association.to_s + '_id').to_sym].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'created journalized associated object' do |
||||
before do |
||||
allow(User).to receive(:current).and_return(user) # in order to have the content journal created by the user |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return(user2) |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by_id(associated_instance.id)).to eq(associated_instance) } |
||||
it 'should keep the current user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(user2) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(substitute_user) } |
||||
it 'should update the first journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details[(association.to_s + '_id').to_sym].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(user2) } |
||||
it 'should update the last journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details[(association.to_s + '_id').to_sym].first).to eq(substitute_user.id) |
||||
expect(associated_instance.journals.last.details[(association.to_s + '_id').to_sym].last).to eq(user2.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user created a meeting' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) { FactoryBot.build(:meeting, project: project) } |
||||
let(:associated_class) { Meeting } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user updated a meeting' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) { FactoryBot.build(:meeting, project: project) } |
||||
let(:associated_class) { Meeting } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user created a meeting agenda' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:meeting_agenda, meeting: meeting, |
||||
text: 'lorem') |
||||
end |
||||
let(:associated_class) { MeetingAgenda } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user updated a meeting agenda' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:meeting_agenda, meeting: meeting, |
||||
text: 'lorem') |
||||
end |
||||
let(:associated_class) { MeetingAgenda } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user created a meeting minutes' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:meeting_minutes, |
||||
meeting: meeting, |
||||
text: 'lorem') |
||||
end |
||||
let(:associated_class) { MeetingMinutes } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user updated a meeting minutes' do |
||||
let(:associations) { [:author] } |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:meeting_minutes, |
||||
meeting: meeting, |
||||
text: 'lorem') |
||||
end |
||||
let(:associated_class) { MeetingMinutes } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user participated in a meeting' do |
||||
before do |
||||
participant |
||||
# user2 added to participants by being the author |
||||
|
||||
user.destroy |
||||
meeting.reload |
||||
participant.reload |
||||
end |
||||
|
||||
it { expect(meeting.participants.map(&:user)).to match_array([DeletedUser.first, user2]) } |
||||
it { expect(participant.invited).to be_truthy } |
||||
it { expect(participant.attended).to be_truthy } |
||||
end |
||||
end |
@ -0,0 +1,41 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class CostQuery::Filter::ResponsibleId < CostQuery::Filter::UserId |
||||
use :null_operators |
||||
join_table WorkPackage |
||||
applies_for :label_work_package_attributes |
||||
|
||||
def self.label |
||||
WorkPackage.human_attribute_name(:responsible) |
||||
end |
||||
|
||||
def self.available_values(*) |
||||
CostQuery::Filter::UserId.available_values |
||||
end |
||||
end |
@ -1,122 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require File.dirname(__FILE__) + '/../../spec_helper' |
||||
|
||||
describe User, "#destroy", type: :model do |
||||
let(:substitute_user) { DeletedUser.first } |
||||
let(:private_query) { FactoryBot.create(:private_cost_query) } |
||||
let(:public_query) { FactoryBot.create(:public_cost_query) } |
||||
let(:user) { FactoryBot.create(:user) } |
||||
let(:user2) { FactoryBot.create(:user) } |
||||
|
||||
describe "WHEN the user has saved private cost queries" do |
||||
before do |
||||
private_query.user.destroy |
||||
end |
||||
|
||||
it { expect(CostQuery.find_by_id(private_query.id)).to eq(nil) } |
||||
end |
||||
|
||||
describe "WHEN the user has saved public cost queries" do |
||||
before do |
||||
public_query.user.destroy |
||||
end |
||||
|
||||
it { expect(CostQuery.find_by_id(public_query.id)).to eq(public_query) } |
||||
it { expect(public_query.reload.user_id).to eq(substitute_user.id) } |
||||
end |
||||
|
||||
shared_examples_for "public query" do |
||||
let(:filter_symbol) { filter.to_s.demodulize.underscore.to_sym } |
||||
|
||||
describe "WHEN the filter has the deleted user as it's value" do |
||||
before do |
||||
public_query.filter(filter_symbol, values: [user.id.to_s], operator: "=") |
||||
public_query.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(CostQuery.find_by_id(public_query.id).deserialize.filters.any? { |f| f.is_a?(filter) }).to be_falsey } |
||||
end |
||||
|
||||
describe "WHEN the filter has another user as it's value" do |
||||
before do |
||||
public_query.filter(filter_symbol, values: [user2.id.to_s], operator: "=") |
||||
public_query.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(CostQuery.find_by_id(public_query.id).deserialize.filters.any? { |f| f.is_a?(filter) }).to be_truthy } |
||||
it { |
||||
expect(CostQuery.find_by_id(public_query.id).deserialize.filters.detect do |f| |
||||
f.is_a?(filter) |
||||
end.values).to eq([user2.id.to_s]) |
||||
} |
||||
end |
||||
|
||||
describe "WHEN the filter has the deleted user and another user as it's value" do |
||||
before do |
||||
public_query.filter(filter_symbol, values: [user.id.to_s, user2.id.to_s], operator: "=") |
||||
public_query.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(CostQuery.find_by_id(public_query.id).deserialize.filters.any? { |f| f.is_a?(filter) }).to be_truthy } |
||||
it { |
||||
expect(CostQuery.find_by_id(public_query.id).deserialize.filters.detect do |f| |
||||
f.is_a?(filter) |
||||
end.values).to eq([user2.id.to_s]) |
||||
} |
||||
end |
||||
end |
||||
|
||||
describe "WHEN someone has saved a public cost query |
||||
WHEN the query has a user_id filter" do |
||||
let(:filter) { CostQuery::Filter::UserId } |
||||
|
||||
it_should_behave_like "public query" |
||||
end |
||||
|
||||
describe "WHEN someone has saved a public cost query |
||||
WHEN the query has a author_id filter" do |
||||
let(:filter) { CostQuery::Filter::AuthorId } |
||||
|
||||
it_should_behave_like "public query" |
||||
end |
||||
|
||||
describe "WHEN someone has saved a public cost query |
||||
WHEN the query has a assigned_to_id filter" do |
||||
let(:filter) { CostQuery::Filter::AssignedToId } |
||||
|
||||
it_should_behave_like "public query" |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_attachment_journal, class: Journal::AttachmentJournal do |
||||
end |
||||
end |
@ -0,0 +1,34 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_changeset_journal, class: Journal::ChangesetJournal do |
||||
revision { 5 } |
||||
committed_on { Time.zone.today } |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_customizable_journal, class: Journal::CustomizableJournal do |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryBot.define do |
||||
factory :journal_news_journal, class: Journal::NewsJournal do |
||||
end |
||||
end |
@ -1,464 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe User, 'deletion', type: :model do |
||||
let(:project) { FactoryBot.create(:project_with_types) } |
||||
let(:user) { FactoryBot.create(:user, member_in_project: project) } |
||||
let(:user2) { FactoryBot.create(:user) } |
||||
let(:member) { project.members.first } |
||||
let(:role) { member.roles.first } |
||||
let(:status) { FactoryBot.create(:status) } |
||||
let(:issue) do |
||||
FactoryBot.create(:work_package, type: project.types.first, |
||||
author: user, |
||||
project: project, |
||||
status: status, |
||||
assigned_to: user) |
||||
end |
||||
let(:issue2) do |
||||
FactoryBot.create(:work_package, type: project.types.first, |
||||
author: user2, |
||||
project: project, |
||||
status: status, |
||||
assigned_to: user2) |
||||
end |
||||
|
||||
let(:substitute_user) { DeletedUser.first } |
||||
|
||||
describe 'WHEN there is the user' do |
||||
before do |
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(User.find_by(id: user.id)).to be_nil } |
||||
end |
||||
|
||||
shared_examples_for 'updated journalized associated object' do |
||||
before do |
||||
allow(User).to receive(:current).and_return user2 |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return user # in order to have the content journal created by the user |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by(id: associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(substitute_user) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(user2) } |
||||
it 'should update first journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details[association_key association].last).to eq(user2.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(substitute_user) } |
||||
it 'should update second journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details[association_key association].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
def association_key(association) |
||||
"#{association}_id".parameterize.underscore.to_sym |
||||
end |
||||
|
||||
shared_examples_for 'created associated object' do |
||||
before do |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by(id: associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(substitute_user) |
||||
end |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'created journalized associated object' do |
||||
before do |
||||
allow(User).to receive(:current).and_return user # in order to have the content journal created by the user |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return user2 |
||||
associated_instance.reload |
||||
associations.each do |association| |
||||
associated_instance.send(association.to_s + '=', user2) |
||||
end |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by(id: associated_instance.id)).to eq(associated_instance) } |
||||
it 'should keep the current user on all associations' do |
||||
associations.each do |association| |
||||
expect(associated_instance.send(association)).to eq(user2) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(substitute_user) } |
||||
it 'should update the first journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details[association_key association].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(user2) } |
||||
it 'should update the last journal' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details[association_key association].first).to eq(substitute_user.id) |
||||
expect(associated_instance.journals.last.details[association_key association].last).to eq(user2.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user has created one attachment' do |
||||
let(:associated_instance) { FactoryBot.build(:attachment) } |
||||
let(:associated_class) { Attachment } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has updated one attachment' do |
||||
let(:associated_instance) { FactoryBot.build(:attachment) } |
||||
let(:associated_class) { Attachment } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has an issue created and assigned' do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:work_package, type: project.types.first, |
||||
project: project, |
||||
status: status) |
||||
end |
||||
let(:associated_class) { WorkPackage } |
||||
let(:associations) { %i[author assigned_to responsible] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has an issue updated and assigned' do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:work_package, type: project.types.first, |
||||
project: project, |
||||
status: status) |
||||
end |
||||
let(:associated_class) { WorkPackage } |
||||
let(:associations) { %i[author assigned_to responsible] } |
||||
|
||||
before do |
||||
allow(User).to receive(:current).and_return user2 |
||||
associated_instance.author = user2 |
||||
associated_instance.assigned_to = user2 |
||||
associated_instance.responsible = user2 |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return user # in order to have the content journal created by the user |
||||
associated_instance.reload |
||||
associated_instance.author = user |
||||
associated_instance.assigned_to = user |
||||
associated_instance.responsible = user |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by(id: associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
expect(associated_instance.author).to eq(substitute_user) |
||||
expect(associated_instance.assigned_to).to be_nil |
||||
expect(associated_instance.responsible).to be_nil |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(user2) } |
||||
it 'should update first journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.first.details[association_key association].last).to eq(user2.id) |
||||
end |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(substitute_user) } |
||||
it 'should update second journal changes' do |
||||
associations.each do |association| |
||||
expect(associated_instance.journals.last.details[association_key association].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user has updated a wiki content' do |
||||
let(:associated_instance) { FactoryBot.build(:wiki_content) } |
||||
let(:associated_class) { WikiContent } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has created a wiki content' do |
||||
let(:associated_instance) { FactoryBot.build(:wiki_content) } |
||||
let(:associated_class) { WikiContent } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has created a news' do |
||||
let(:associated_instance) { FactoryBot.build(:news) } |
||||
let(:associated_class) { News } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has worked on news' do |
||||
let(:associated_instance) { FactoryBot.build(:news) } |
||||
let(:associated_class) { News } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has created a message' do |
||||
let(:associated_instance) { FactoryBot.build(:message) } |
||||
let(:associated_class) { Message } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has worked on message' do |
||||
let(:associated_instance) { FactoryBot.build(:message) } |
||||
let(:associated_class) { Message } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has created a time entry' do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:time_entry, project: project, |
||||
work_package: issue, |
||||
hours: 2, |
||||
activity: FactoryBot.create(:time_entry_activity)) |
||||
end |
||||
let(:associated_class) { TimeEntry } |
||||
let(:associations) { [:user] } |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has worked on time_entry' do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:time_entry, project: project, |
||||
work_package: issue, |
||||
hours: 2, |
||||
activity: FactoryBot.create(:time_entry_activity)) |
||||
end |
||||
let(:associated_class) { TimeEntry } |
||||
let(:associations) { [:user] } |
||||
|
||||
it_should_behave_like 'updated journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has commented' do |
||||
let(:news) { FactoryBot.create(:news, author: user) } |
||||
|
||||
let(:associated_instance) do |
||||
Comment.new(commented: news, |
||||
comments: 'lorem') |
||||
end |
||||
|
||||
let(:associated_class) { Comment } |
||||
let(:associations) { [:author] } |
||||
|
||||
it_should_behave_like 'created associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user is a member of a project' do |
||||
before do |
||||
user |
||||
member |
||||
end |
||||
|
||||
it 'removes that member' do |
||||
user.destroy |
||||
|
||||
expect(Member.find_by(id: member.id)).to be_nil |
||||
expect(Role.find_by(id: role.id)).to eq(role) |
||||
expect(Project.find_by(id: project.id)).to eq(project) |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user is watching something' do |
||||
let(:watched) { FactoryBot.create(:work_package, project: project) } |
||||
let(:watch) do |
||||
Watcher.new(user: user, |
||||
watchable: watched) |
||||
end |
||||
|
||||
before do |
||||
watch.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(Watcher.find_by(id: watch.id)).to be_nil } |
||||
end |
||||
|
||||
describe 'WHEN the user has a token created' do |
||||
let(:token) do |
||||
Token::RSS.new(user: user, value: 'loremipsum') |
||||
end |
||||
|
||||
before do |
||||
token.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(Token::RSS.find_by(id: token.id)).to be_nil } |
||||
end |
||||
|
||||
describe 'WHEN the user has created a private query' do |
||||
let(:query) { FactoryBot.build(:private_query, user: user) } |
||||
|
||||
before do |
||||
query.save! |
||||
|
||||
user.destroy |
||||
end |
||||
|
||||
it { expect(Query.find_by(id: query.id)).to be_nil } |
||||
end |
||||
|
||||
describe 'WHEN the user has created a public query' do |
||||
let(:associated_instance) { FactoryBot.build(:public_query) } |
||||
|
||||
let(:associated_class) { Query } |
||||
let(:associations) { [:user] } |
||||
|
||||
it_should_behave_like 'created associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has created a changeset' do |
||||
with_virtual_subversion_repository do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:changeset, |
||||
repository_id: repository.id, |
||||
committer: user.login) |
||||
end |
||||
|
||||
let(:associated_class) { Changeset } |
||||
let(:associations) { [:user] } |
||||
end |
||||
|
||||
it_should_behave_like 'created journalized associated object' |
||||
end |
||||
|
||||
describe 'WHEN the user has updated a changeset' do |
||||
with_virtual_subversion_repository do |
||||
let(:associated_instance) do |
||||
FactoryBot.build(:changeset, |
||||
repository_id: repository.id, |
||||
committer: user2.login) |
||||
end |
||||
end |
||||
|
||||
let(:associated_class) { Changeset } |
||||
let(:associations) { [:user] } |
||||
|
||||
before do |
||||
allow(User).to receive(:current).and_return user2 |
||||
associated_instance.user = user2 |
||||
associated_instance.save! |
||||
|
||||
allow(User).to receive(:current).and_return user # in order to have the content journal created by the user |
||||
associated_instance.reload |
||||
associated_instance.user = user |
||||
associated_instance.save! |
||||
|
||||
user.destroy |
||||
associated_instance.reload |
||||
end |
||||
|
||||
it { expect(associated_class.find_by(id: associated_instance.id)).to eq(associated_instance) } |
||||
it 'should replace the user on all associations' do |
||||
expect(associated_instance.user).to be_nil |
||||
end |
||||
it { expect(associated_instance.journals.first.user).to eq(user2) } |
||||
it 'should update first journal changes' do |
||||
expect(associated_instance.journals.first.details[:user_id].last).to eq(user2.id) |
||||
end |
||||
it { expect(associated_instance.journals.last.user).to eq(substitute_user) } |
||||
it 'should update second journal changes' do |
||||
expect(associated_instance.journals.last.details[:user_id].last).to eq(substitute_user.id) |
||||
end |
||||
end |
||||
|
||||
describe 'WHEN the user is assigned an issue category' do |
||||
let(:category) do |
||||
FactoryBot.build(:category, assigned_to: user, |
||||
project: project) |
||||
end |
||||
|
||||
before do |
||||
category.save! |
||||
user.destroy |
||||
category.reload |
||||
end |
||||
|
||||
it { expect(Category.find_by(id: category.id)).to eq(category) } |
||||
it { expect(category.assigned_to).to be_nil } |
||||
end |
||||
end |
@ -1,106 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Journals::UserReferenceUpdateService, type: :model do |
||||
let!(:work_package) { FactoryBot.create :work_package } |
||||
let!(:doomed_user) { work_package.author } |
||||
let!(:other_user) { FactoryBot.create(:user) } |
||||
let!(:data1) do |
||||
FactoryBot.build(:journal_work_package_journal, |
||||
subject: work_package.subject, |
||||
status_id: work_package.status_id, |
||||
type_id: work_package.type_id, |
||||
author_id: doomed_user.id, |
||||
assigned_to_id: other_user.id, |
||||
responsible_id: doomed_user.id, |
||||
project_id: work_package.project_id) |
||||
end |
||||
let!(:data2) do |
||||
FactoryBot.build(:journal_work_package_journal, |
||||
subject: work_package.subject, |
||||
status_id: work_package.status_id, |
||||
type_id: work_package.type_id, |
||||
author_id: doomed_user.id, |
||||
assigned_to_id: doomed_user.id, |
||||
responsible_id: other_user.id, |
||||
project_id: work_package.project_id) |
||||
end |
||||
let!(:doomed_user_journal) do |
||||
FactoryBot.create :work_package_journal, |
||||
notes: '1', |
||||
user: doomed_user, |
||||
journable_id: work_package.id, |
||||
data: data1 |
||||
end |
||||
let!(:some_other_journal) do |
||||
FactoryBot.create :work_package_journal, |
||||
notes: '2', |
||||
journable_id: work_package.id, |
||||
data: data2 |
||||
end |
||||
|
||||
describe '.call' do |
||||
subject do |
||||
described_class |
||||
.new(doomed_user) |
||||
.call(DeletedUser.first) |
||||
end |
||||
|
||||
before do |
||||
subject |
||||
end |
||||
|
||||
it "is success" do |
||||
expect(subject) |
||||
.to be_success |
||||
end |
||||
|
||||
it "marks only the user's journal as deleted" do |
||||
expect(doomed_user_journal.reload.user.is_a?(DeletedUser)).to be_truthy |
||||
expect(some_other_journal.reload.user.is_a?(DeletedUser)).to be_falsey |
||||
end |
||||
|
||||
it "marks the assignee stored in the WorkPackageJournal as deleted" do |
||||
expect(data2.reload.assigned_to_id) |
||||
.to eql(DeletedUser.first.id) |
||||
|
||||
expect(data1.reload.assigned_to_id) |
||||
.to eql(other_user.id) |
||||
end |
||||
|
||||
it "marks the responsible stored in the WorkPackageJournal as deleted" do |
||||
expect(data1.reload.responsible_id) |
||||
.to eql(DeletedUser.first.id) |
||||
|
||||
expect(data2.reload.responsible_id) |
||||
.to eql(other_user.id) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,408 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Principals::ReplaceReferencesService, '#call', type: :model do |
||||
subject(:service_call) { instance.call(from: principal, to: to_principal) } |
||||
|
||||
shared_let(:other_user) { FactoryBot.create(:user) } |
||||
shared_let(:user) { FactoryBot.create(:user) } |
||||
shared_let(:to_principal) { FactoryBot.create :user } |
||||
|
||||
let(:instance) do |
||||
described_class.new |
||||
end |
||||
|
||||
context 'with a user' do |
||||
let(:principal) { user } |
||||
|
||||
it 'is successful' do |
||||
expect(service_call) |
||||
.to be_success |
||||
end |
||||
|
||||
context 'with a Journal' do |
||||
let!(:journal) do |
||||
FactoryBot.create(:work_package_journal, |
||||
user_id: user_id, |
||||
data: instance_double(Journal::WorkPackageJournal, |
||||
'journal=': nil, |
||||
save: true)) |
||||
end |
||||
|
||||
context 'with the replaced user' do |
||||
let(:user_id) { principal.id } |
||||
|
||||
before do |
||||
service_call |
||||
journal.reload |
||||
end |
||||
|
||||
it 'replaces user_id' do |
||||
expect(journal.user_id) |
||||
.to eql to_principal.id |
||||
end |
||||
end |
||||
|
||||
context 'with a different user' do |
||||
let(:user_id) { other_user.id } |
||||
|
||||
before do |
||||
service_call |
||||
journal.reload |
||||
end |
||||
|
||||
it 'replaces user_id' do |
||||
expect(journal.user_id) |
||||
.to eql other_user.id |
||||
end |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'rewritten record' do |factory, attribute, format = Integer| |
||||
let!(:model) do |
||||
klass = FactoryBot.factories.find(factory).build_class |
||||
all_attributes = other_attributes.merge(attribute => principal_id) |
||||
|
||||
inserted = ActiveRecord::Base.connection.select_one <<~SQL |
||||
INSERT INTO #{klass.table_name} |
||||
(#{all_attributes.keys.join(', ')}) |
||||
VALUES |
||||
(#{all_attributes.values.join(', ')}) |
||||
RETURNING id |
||||
SQL |
||||
|
||||
klass.find(inserted['id']) |
||||
end |
||||
|
||||
let(:other_attributes) do |
||||
defined?(attributes) ? attributes : {} |
||||
end |
||||
|
||||
def expected(user, format) |
||||
if format == String |
||||
user.id.to_s |
||||
else |
||||
user.id |
||||
end |
||||
end |
||||
|
||||
context "for #{factory}" do |
||||
context 'with the replaced user' do |
||||
let(:principal_id) { principal.id } |
||||
|
||||
before do |
||||
service_call |
||||
model.reload |
||||
end |
||||
|
||||
it "replaces #{attribute}" do |
||||
expect(model.send(attribute)) |
||||
.to eql expected(to_principal, format) |
||||
end |
||||
end |
||||
|
||||
context 'with a different user' do |
||||
let(:principal_id) { other_user.id } |
||||
|
||||
before do |
||||
service_call |
||||
model.reload |
||||
end |
||||
|
||||
it "keeps #{attribute}" do |
||||
expect(model.send(attribute)) |
||||
.to eql expected(other_user, format) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Attachment' do |
||||
it_behaves_like 'rewritten record', |
||||
:attachment, |
||||
:author_id |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_attachment_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Comment' do |
||||
it_behaves_like 'rewritten record', |
||||
:comment, |
||||
:author_id |
||||
end |
||||
|
||||
context 'with CustomValue' do |
||||
it_behaves_like 'rewritten record', |
||||
:custom_value, |
||||
:value, |
||||
String do |
||||
let(:user_cf) { FactoryBot.create(:user_wp_custom_field) } |
||||
let(:attributes) do |
||||
{ custom_field_id: user_cf.id } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_customizable_journal, |
||||
:value, |
||||
String do |
||||
let(:user_cf) { FactoryBot.create(:user_wp_custom_field) } |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
custom_field_id: user_cf.id } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Changeset' do |
||||
it_behaves_like 'rewritten record', |
||||
:changeset, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ repository_id: 1, |
||||
revision: 1, |
||||
committed_on: "date '2012-02-02'" } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_changeset_journal, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
repository_id: 1, |
||||
revision: 1, |
||||
committed_on: "date '2012-02-02'" } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Message' do |
||||
it_behaves_like 'rewritten record', |
||||
:message, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ forum_id: 1 } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_message_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
forum_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with MeetingContent' do |
||||
it_behaves_like 'rewritten record', |
||||
:meeting_agenda, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ type: "'MeetingAgenda'", |
||||
created_at: 'NOW()', |
||||
updated_at: 'NOW()' } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:meeting_minutes, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ type: "'MeetingMinutes'", |
||||
created_at: 'NOW()', |
||||
updated_at: 'NOW()' } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_meeting_content_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with MeetingParticipant' do |
||||
it_behaves_like 'rewritten record', |
||||
:meeting_participant, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ created_at: 'NOW()', |
||||
updated_at: 'NOW()' } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with News' do |
||||
it_behaves_like 'rewritten record', |
||||
:news, |
||||
:author_id |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_news_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with WikiContent' do |
||||
it_behaves_like 'rewritten record', |
||||
:wiki_content, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ page_id: 1, |
||||
lock_version: 5 } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_wiki_content_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
page_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with WorkPackage' do |
||||
it_behaves_like 'rewritten record', |
||||
:work_package, |
||||
:assigned_to_id |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:work_package, |
||||
:responsible_id |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_work_package_journal, |
||||
:assigned_to_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1 } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_work_package_journal, |
||||
:responsible_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with TimeEntry' do |
||||
it_behaves_like 'rewritten record', |
||||
:time_entry, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ project_id: 1, |
||||
hours: 5, |
||||
activity_id: 1, |
||||
spent_on: "date '2012-02-02'", |
||||
tyear: 2021, |
||||
tmonth: 12, |
||||
tweek: 5 } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_time_entry_journal, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
project_id: 1, |
||||
hours: 5, |
||||
activity_id: 1, |
||||
spent_on: "date '2012-02-02'", |
||||
tyear: 2021, |
||||
tmonth: 12, |
||||
tweek: 5 } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Budget' do |
||||
it_behaves_like 'rewritten record', |
||||
:budget, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ project_id: 1, |
||||
subject: "'abc'", |
||||
description: "'cde'", |
||||
fixed_date: "date '2012-02-02'" } |
||||
end |
||||
end |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:journal_budget_journal, |
||||
:author_id do |
||||
let(:attributes) do |
||||
{ journal_id: 1, |
||||
project_id: 1, |
||||
subject: "'abc'", |
||||
fixed_date: "date '2012-02-02'" } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with Query' do |
||||
it_behaves_like 'rewritten record', |
||||
:query, |
||||
:user_id |
||||
end |
||||
|
||||
context 'with CostQuery' do |
||||
let(:query) { FactoryBot.create(:cost_query, user: principal) } |
||||
|
||||
it_behaves_like 'rewritten record', |
||||
:cost_query, |
||||
:user_id do |
||||
let(:attributes) do |
||||
{ name: "'abc'", |
||||
serialized: "'cde'" } |
||||
end |
||||
end |
||||
|
||||
end |
||||
end |
||||
end |
@ -0,0 +1,404 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Principals::DeleteJob, type: :model do |
||||
subject(:job) { described_class.perform_now(principal) } |
||||
|
||||
shared_let(:project) { FactoryBot.create(:project) } |
||||
|
||||
shared_let(:deleted_user) do |
||||
FactoryBot.create(:deleted_user) |
||||
end |
||||
let(:principal) do |
||||
FactoryBot.create(:user) |
||||
end |
||||
let(:member) do |
||||
FactoryBot.create(:member, |
||||
principal: principal, |
||||
project: project, |
||||
roles: [role]) |
||||
end |
||||
shared_let(:role) do |
||||
FactoryBot.create(:role, permissions: %i[view_work_packages] ) |
||||
end |
||||
|
||||
describe '#perform' do |
||||
# These are the only tests that include testing |
||||
# the ReplaceReferencesService. Most of the tests for this |
||||
# Service are handled within the matching spec file. |
||||
shared_examples_for 'work_package handling' do |
||||
let(:work_package) do |
||||
FactoryBot.create(:work_package, |
||||
assigned_to: principal, |
||||
responsible: principal) |
||||
end |
||||
|
||||
before do |
||||
work_package |
||||
job |
||||
end |
||||
|
||||
it 'resets assigned to to the deleted user' do |
||||
expect(work_package.reload.assigned_to) |
||||
.to eql(deleted_user) |
||||
end |
||||
|
||||
it 'resets assigned to in all journals to the deleted user' do |
||||
expect(Journal::WorkPackageJournal.pluck(:assigned_to_id)) |
||||
.to eql([deleted_user.id]) |
||||
end |
||||
|
||||
it 'resets responsible to to the deleted user' do |
||||
expect(work_package.reload.responsible) |
||||
.to eql(deleted_user) |
||||
end |
||||
|
||||
it 'resets responsible to in all journals to the deleted user' do |
||||
expect(Journal::WorkPackageJournal.pluck(:responsible_id)) |
||||
.to eql([deleted_user.id]) |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'labor_budget_item handling' do |
||||
let(:item) { FactoryBot.build(:labor_budget_item, user: principal) } |
||||
|
||||
before do |
||||
item.save! |
||||
|
||||
job |
||||
end |
||||
|
||||
it { expect(LaborBudgetItem.find_by_id(item.id)).to eq(item) } |
||||
it { expect(item.user_id).to eq(principal.id) } |
||||
end |
||||
|
||||
shared_examples_for 'cost_entry handling' do |
||||
let(:work_package) { FactoryBot.create(:work_package) } |
||||
let(:entry) do |
||||
FactoryBot.create(:cost_entry, |
||||
user: principal, |
||||
project: work_package.project, |
||||
units: 100.0, |
||||
spent_on: Date.today, |
||||
work_package: work_package, |
||||
comments: '') |
||||
end |
||||
|
||||
before do |
||||
FactoryBot.create(:member, |
||||
project: work_package.project, |
||||
user: principal, |
||||
roles: [FactoryBot.build(:role)]) |
||||
entry |
||||
|
||||
job |
||||
|
||||
entry.reload |
||||
end |
||||
|
||||
it { expect(entry.user_id).to eq(principal.id) } |
||||
end |
||||
|
||||
shared_examples_for 'member handling' do |
||||
before do |
||||
member |
||||
|
||||
job |
||||
end |
||||
|
||||
it 'removes that member' do |
||||
expect(Member.find_by(id: member.id)).to be_nil |
||||
end |
||||
|
||||
it 'leaves the role' do |
||||
expect(Role.find_by(id: role.id)).to eq(role) |
||||
end |
||||
|
||||
it 'leaves the project' do |
||||
expect(Project.find_by(id: project.id)).to eq(project) |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'hourly_rate handling' do |
||||
let(:hourly_rate) do |
||||
FactoryBot.build(:hourly_rate, |
||||
user: principal, |
||||
project: project) |
||||
end |
||||
|
||||
before do |
||||
hourly_rate.save! |
||||
job |
||||
end |
||||
|
||||
it { expect(HourlyRate.find_by_id(hourly_rate.id)).to eq(hourly_rate) } |
||||
it { expect(hourly_rate.reload.user_id).to eq(principal.id) } |
||||
end |
||||
|
||||
shared_examples_for 'watcher handling' do |
||||
let(:watched) { FactoryBot.create(:news, project: project) } |
||||
let(:watch) do |
||||
Watcher.create(user: principal, |
||||
watchable: watched) |
||||
end |
||||
|
||||
before do |
||||
member |
||||
watch |
||||
|
||||
job |
||||
end |
||||
|
||||
it { expect(Watcher.find_by(id: watch.id)).to be_nil } |
||||
end |
||||
|
||||
shared_examples_for 'token handling' do |
||||
let(:token) do |
||||
Token::RSS.new(user: principal, value: 'loremipsum') |
||||
end |
||||
|
||||
before do |
||||
token.save! |
||||
|
||||
job |
||||
end |
||||
|
||||
it { expect(Token::RSS.find_by(id: token.id)).to be_nil } |
||||
end |
||||
|
||||
shared_examples_for 'private query handling' do |
||||
let!(:query) do |
||||
FactoryBot.create(:private_query, user: principal) |
||||
end |
||||
|
||||
before do |
||||
job |
||||
end |
||||
|
||||
it { expect(Query.find_by(id: query.id)).to be_nil } |
||||
end |
||||
|
||||
shared_examples_for 'issue category handling' do |
||||
let(:category) do |
||||
FactoryBot.create(:category, |
||||
assigned_to: principal, |
||||
project: project) |
||||
end |
||||
|
||||
before do |
||||
member |
||||
category |
||||
job |
||||
end |
||||
|
||||
it 'does not remove the category' do |
||||
expect(Category.find_by(id: category.id)).to eq(category) |
||||
end |
||||
|
||||
it 'removes the assigned_to association to the principal' do |
||||
expect(category.reload.assigned_to).to be_nil |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'removes the principal' do |
||||
it 'deletes the principal' do |
||||
job |
||||
|
||||
expect(Principal.find_by(id: principal.id)) |
||||
.to be_nil |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'private cost_query handling' do |
||||
let!(:query) { FactoryBot.create(:private_cost_query, user: principal) } |
||||
|
||||
it 'removes the query' do |
||||
job |
||||
|
||||
expect(CostQuery.find_by_id(query.id)).to eq(nil) |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'public cost_query handling' do |
||||
let!(:query) { FactoryBot.create(:public_cost_query, user: principal) } |
||||
|
||||
before do |
||||
query |
||||
|
||||
job |
||||
end |
||||
|
||||
it 'leaves the query' do |
||||
expect(CostQuery.find_by_id(query.id)).to eq(query) |
||||
end |
||||
|
||||
it 'rewrites the user reference' do |
||||
expect(query.reload.user).to eq(deleted_user) |
||||
end |
||||
end |
||||
|
||||
shared_examples_for 'cost_query handling' do |
||||
let(:query) { FactoryBot.create(:cost_query) } |
||||
let(:other_user) { FactoryBot.create(:user) } |
||||
|
||||
shared_examples_for "public query rewriting" do |
||||
let(:filter_symbol) { filter.to_s.demodulize.underscore.to_sym } |
||||
|
||||
describe "with the filter has the deleted user as it's value" do |
||||
before do |
||||
query.filter(filter_symbol, values: [principal.id.to_s], operator: "=") |
||||
query.save! |
||||
|
||||
job |
||||
end |
||||
|
||||
it 'removes the filter' do |
||||
expect(CostQuery.find_by(id: query.id).deserialize.filters) |
||||
.not_to(be_any { |f| f.is_a?(filter) }) |
||||
end |
||||
end |
||||
|
||||
describe "with the filter has another user as it's value" do |
||||
before do |
||||
query.filter(filter_symbol, values: [other_user.id.to_s], operator: "=") |
||||
query.save! |
||||
|
||||
job |
||||
end |
||||
|
||||
it 'keeps the filter' do |
||||
expect(CostQuery.find_by(id: query.id).deserialize.filters) |
||||
.to(be_any { |f| f.is_a?(filter) }) |
||||
end |
||||
|
||||
it 'does not alter the filter values' do |
||||
expect(CostQuery.find_by(id: query.id).deserialize.filters.detect do |f| |
||||
f.is_a?(filter) |
||||
end.values).to eq([other_user.id.to_s]) |
||||
end |
||||
end |
||||
|
||||
describe "with the filter has the deleted user and another user as it's value" do |
||||
before do |
||||
query.filter(filter_symbol, values: [principal.id.to_s, other_user.id.to_s], operator: "=") |
||||
query.save! |
||||
|
||||
job |
||||
end |
||||
|
||||
it 'keeps the filter' do |
||||
expect(CostQuery.find_by(id: query.id).deserialize.filters) |
||||
.to(be_any { |f| f.is_a?(filter) }) |
||||
end |
||||
|
||||
it 'removes only the deleted user' do |
||||
expect(CostQuery.find_by(id: query.id).deserialize.filters.detect do |f| |
||||
f.is_a?(filter) |
||||
end.values).to eq([other_user.id.to_s]) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe "with the query has a user_id filter" do |
||||
let(:filter) { CostQuery::Filter::UserId } |
||||
|
||||
it_should_behave_like "public query rewriting" |
||||
end |
||||
|
||||
describe "with the query has a author_id filter" do |
||||
let(:filter) { CostQuery::Filter::AuthorId } |
||||
|
||||
it_should_behave_like "public query rewriting" |
||||
end |
||||
|
||||
describe "with the query has a assigned_to_id filter" do |
||||
let(:filter) { CostQuery::Filter::AssignedToId } |
||||
|
||||
it_should_behave_like "public query rewriting" |
||||
end |
||||
|
||||
describe "with the query has an responsible_id filter" do |
||||
let(:filter) { CostQuery::Filter::ResponsibleId } |
||||
|
||||
it_should_behave_like "public query rewriting" |
||||
end |
||||
end |
||||
|
||||
context 'with a user' do |
||||
it_behaves_like 'removes the principal' |
||||
it_behaves_like 'work_package handling' |
||||
it_behaves_like 'labor_budget_item handling' |
||||
it_behaves_like 'cost_entry handling' |
||||
it_behaves_like 'hourly_rate handling' |
||||
it_behaves_like 'member handling' |
||||
it_behaves_like 'watcher handling' |
||||
it_behaves_like 'token handling' |
||||
it_behaves_like 'private query handling' |
||||
it_behaves_like 'issue category handling' |
||||
it_behaves_like 'private cost_query handling' |
||||
it_behaves_like 'public cost_query handling' |
||||
it_behaves_like 'cost_query handling' |
||||
end |
||||
|
||||
context 'with a group' do |
||||
let(:principal) { FactoryBot.create(:group, members: group_members) } |
||||
let(:group_members) { [] } |
||||
|
||||
it_behaves_like 'removes the principal' |
||||
it_behaves_like 'work_package handling' |
||||
it_behaves_like 'member handling' |
||||
|
||||
context 'with user only in project through group' do |
||||
let(:user) do |
||||
FactoryBot.create(:user) |
||||
end |
||||
let(:group_members) { [user] } |
||||
let(:watched) { FactoryBot.create(:news, project: project) } |
||||
let(:watch) do |
||||
Watcher.create(user: user, |
||||
watchable: watched) |
||||
end |
||||
|
||||
it 'removes the watcher' do |
||||
job |
||||
|
||||
expect(watched.watchers.reload).to be_empty |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'with a placeholder user' do |
||||
let(:principal) { FactoryBot.create(:placeholder_user) } |
||||
|
||||
it_behaves_like 'removes the principal' |
||||
it_behaves_like 'work_package handling' |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue