mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[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>
This commit is contained in:
@@ -80,9 +80,9 @@ class GroupsController < ApplicationController
|
||||
end
|
||||
|
||||
def destroy
|
||||
@group.destroy
|
||||
::Principals::DeleteJob.perform_later(@group)
|
||||
|
||||
flash[:notice] = I18n.t(:notice_successful_delete)
|
||||
flash[:info] = I18n.t(:notice_deletion_scheduled)
|
||||
redirect_to action: :index
|
||||
end
|
||||
|
||||
|
||||
@@ -120,11 +120,11 @@ class PlaceholderUsersController < ApplicationController
|
||||
end
|
||||
|
||||
def destroy
|
||||
Users::DeleteService.new(user: User.current,
|
||||
model: @placeholder_user)
|
||||
.call
|
||||
PlaceholderUsers::DeleteService
|
||||
.new(user: User.current, model: @placeholder_user)
|
||||
.call
|
||||
|
||||
flash[:notice] = I18n.t('account.deleted')
|
||||
flash[:info] = I18n.t(:notice_deletion_scheduled)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@@ -142,7 +142,7 @@ class PlaceholderUsersController < ApplicationController
|
||||
end
|
||||
|
||||
def check_if_deletion_allowed
|
||||
render_404 unless PlaceholderUsers::DeleteService.deletion_allowed? @placeholder_user, User.current
|
||||
render_404 unless PlaceholderUsers::DeleteContract.deletion_allowed?(current_user)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
module Associations::Groupable
|
||||
def self.included(base)
|
||||
base.has_and_belongs_to_many :groups,
|
||||
foreign_key: 'user_id',
|
||||
join_table: "#{base.table_name_prefix}group_users#{base.table_name_suffix}",
|
||||
after_remove: ->(user, group) { group.user_removed(user) }
|
||||
end
|
||||
|
||||
@@ -36,8 +36,6 @@ class Group < Principal
|
||||
|
||||
acts_as_customizable
|
||||
|
||||
before_destroy :remove_references_before_destroy
|
||||
|
||||
alias_attribute(:groupname, :lastname)
|
||||
validates_presence_of :groupname
|
||||
validate :uniqueness_of_groupname
|
||||
@@ -94,18 +92,6 @@ class Group < Principal
|
||||
|
||||
private
|
||||
|
||||
# Removes references that are not handled by associations
|
||||
def remove_references_before_destroy
|
||||
return if id.nil?
|
||||
|
||||
deleted_user = DeletedUser.first
|
||||
|
||||
WorkPackage.where(assigned_to_id: id).update_all(assigned_to_id: deleted_user.id)
|
||||
|
||||
Journal::WorkPackageJournal.where(assigned_to_id: id)
|
||||
.update_all(assigned_to_id: deleted_user.id)
|
||||
end
|
||||
|
||||
def uniqueness_of_groupname
|
||||
groups_with_name = Group.where('lastname = ? AND id <> ?', groupname, id || 0).count
|
||||
if groups_with_name > 0
|
||||
|
||||
@@ -134,8 +134,6 @@ class User < Principal
|
||||
after_save :update_password
|
||||
|
||||
before_create :sanitize_mail_notification_setting
|
||||
before_destroy :delete_associated_private_queries
|
||||
before_destroy :reassign_associated
|
||||
|
||||
scope :admin, -> { where(admin: true) }
|
||||
|
||||
@@ -733,26 +731,6 @@ class User < Principal
|
||||
(passwords[keep_count..-1] || []).each(&:destroy)
|
||||
end
|
||||
|
||||
def reassign_associated
|
||||
substitute = DeletedUser.first
|
||||
|
||||
[WorkPackage, Attachment, WikiContent, News, Comment, Message].each do |klass|
|
||||
klass.where(['author_id = ?', id]).update_all ['author_id = ?', substitute.id]
|
||||
end
|
||||
|
||||
[TimeEntry, ::Query].each do |klass|
|
||||
klass.where(['user_id = ?', id]).update_all ['user_id = ?', substitute.id]
|
||||
end
|
||||
|
||||
Journals::UserReferenceUpdateService
|
||||
.new(self)
|
||||
.call(substitute)
|
||||
end
|
||||
|
||||
def delete_associated_private_queries
|
||||
::Query.where(user_id: id, is_public: false).delete_all
|
||||
end
|
||||
|
||||
##
|
||||
# Brute force prevention - class methods
|
||||
#
|
||||
|
||||
@@ -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
|
||||
@@ -29,4 +29,9 @@
|
||||
#++
|
||||
|
||||
class PlaceholderUsers::DeleteService < ::BaseServices::Delete
|
||||
def destroy(placeholder)
|
||||
::Principals::DeleteJob.perform_later(placeholder)
|
||||
|
||||
true
|
||||
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
|
||||
@@ -40,7 +40,7 @@ module Users
|
||||
# as destroying users is a lengthy process we handle it in the background
|
||||
# and lock the account now so that no action can be performed with it
|
||||
user_object.lock!
|
||||
DeleteUserJob.perform_later(user_object)
|
||||
::Principals::DeleteJob.perform_later(user_object)
|
||||
|
||||
logout! if self_delete?
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1906,6 +1906,7 @@ en:
|
||||
notice_email_sent: "An email was sent to %{value}"
|
||||
notice_failed_to_save_work_packages: "Failed to save %{count} work package(s) on %{total} selected: %{ids}."
|
||||
notice_failed_to_save_members: "Failed to save member(s): %{errors}."
|
||||
notice_deletion_scheduled: "The deletion has been scheduled and is performed asynchronously."
|
||||
|
||||
notice_file_not_found: "The page you were trying to access doesn't exist or has been removed."
|
||||
notice_forced_logout: "You have been automatically logged out after %{ttl_time} minutes of inactivity."
|
||||
|
||||
@@ -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
|
||||
@@ -59,10 +59,6 @@ class Budget < ApplicationRecord
|
||||
validates_length_of :subject, maximum: 255
|
||||
validates_length_of :subject, minimum: 1
|
||||
|
||||
User.before_destroy do |user|
|
||||
Budget.replace_author_with_deleted_user user
|
||||
end
|
||||
|
||||
class << self
|
||||
def visible(user)
|
||||
includes(:project)
|
||||
@@ -80,12 +76,6 @@ class Budget < ApplicationRecord
|
||||
copy
|
||||
end
|
||||
|
||||
def replace_author_with_deleted_user(user)
|
||||
substitute = DeletedUser.first
|
||||
|
||||
where(author_id: user.id).update_all(author_id: substitute.id)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def copy_attributes(source)
|
||||
|
||||
+2
-7
@@ -1,5 +1,3 @@
|
||||
#-- encoding: UTF-8
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2021 the OpenProject GmbH
|
||||
@@ -28,10 +26,7 @@
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
class DeleteUserJob < ApplicationJob
|
||||
queue_with_priority :low
|
||||
|
||||
def perform(user)
|
||||
user.destroy
|
||||
FactoryBot.define do
|
||||
factory :journal_budget_journal, class: Journal::BudgetJournal 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_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
|
||||
@@ -89,10 +89,6 @@ class Meeting < ApplicationRecord
|
||||
|
||||
after_initialize :set_initial_values
|
||||
|
||||
User.before_destroy do |user|
|
||||
Meeting.where(['author_id = ?', user.id]).update_all ['author_id = ?', DeletedUser.first.id]
|
||||
end
|
||||
|
||||
##
|
||||
# Return the computed start_time when changed
|
||||
def start_time
|
||||
|
||||
@@ -54,10 +54,6 @@ class MeetingContent < ApplicationRecord
|
||||
title: Proc.new { |o| "#{o.class.model_name.human}: #{o.meeting.title}" },
|
||||
url: Proc.new { |o| { controller: '/meetings', action: 'show', id: o.meeting } }
|
||||
|
||||
User.before_destroy do |user|
|
||||
MeetingContent.where(['author_id = ?', user.id]).update_all ['author_id = ?', DeletedUser.first]
|
||||
end
|
||||
|
||||
def editable?
|
||||
true
|
||||
end
|
||||
|
||||
@@ -33,10 +33,6 @@ class MeetingParticipant < ApplicationRecord
|
||||
scope :invited, -> { where(invited: true) }
|
||||
scope :attended, -> { where(attended: true) }
|
||||
|
||||
User.before_destroy do |user|
|
||||
MeetingParticipant.where(['user_id = ?', user.id]).update_all ['user_id = ?', DeletedUser.first]
|
||||
end
|
||||
|
||||
def name
|
||||
user.present? ? user.name : name
|
||||
end
|
||||
|
||||
@@ -87,12 +87,6 @@ module OpenProject::Meeting
|
||||
end
|
||||
|
||||
config.to_prepare do
|
||||
# load classes so that all User.before_destroy filters are loaded
|
||||
require_dependency 'meeting'
|
||||
require_dependency 'meeting_agenda'
|
||||
require_dependency 'meeting_minutes'
|
||||
require_dependency 'meeting_participant'
|
||||
|
||||
PermittedParams.permit(:search, :meetings)
|
||||
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
|
||||
@@ -249,31 +249,4 @@ class CostQuery < ApplicationRecord
|
||||
def private?
|
||||
!public?
|
||||
end
|
||||
|
||||
User.before_destroy do |user|
|
||||
CostQuery.where(user_id: user.id, is_public: false).delete_all
|
||||
CostQuery.where(['user_id = ?', user.id]).update_all ['user_id = ?', DeletedUser.first.id]
|
||||
|
||||
max_query_id = 0
|
||||
while (current_queries = CostQuery.limit(1000)
|
||||
.where(["id > ?", max_query_id])
|
||||
.order("id ASC")).size > 0
|
||||
|
||||
current_queries.each do |query|
|
||||
serialized = query.serialized
|
||||
|
||||
serialized[:filters] = serialized[:filters].map do |name, options|
|
||||
options[:values].delete(user.id.to_s) if ["UserId", "AuthorId", "AssignedToId"].include?(name)
|
||||
|
||||
if options[:values].nil? || options[:values].size > 0
|
||||
[name, options]
|
||||
end
|
||||
end.compact
|
||||
|
||||
CostQuery.where(["id = ?", query.id]).update_all ["serialized = ?", YAML::dump(serialized)]
|
||||
|
||||
max_query_id = query.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,6 +44,7 @@ class CostQuery::Filter < Report::Filter
|
||||
CostQuery::Filter::OverriddenCosts,
|
||||
CostQuery::Filter::PriorityId,
|
||||
CostQuery::Filter::ProjectId,
|
||||
CostQuery::Filter::ResponsibleId,
|
||||
CostQuery::Filter::SpentOn,
|
||||
CostQuery::Filter::StartDate,
|
||||
CostQuery::Filter::StatusId,
|
||||
|
||||
@@ -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
|
||||
@@ -82,6 +82,9 @@ describe GroupsController, type: :controller do
|
||||
|
||||
it 'should destroy' do
|
||||
delete :destroy, params: { id: group.id }
|
||||
|
||||
perform_enqueued_jobs
|
||||
|
||||
expect { group.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
|
||||
expect(response).to redirect_to groups_path
|
||||
|
||||
@@ -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
|
||||
@@ -44,6 +44,13 @@ feature 'group memberships through groups page', type: :feature do
|
||||
expect(groups_page).to have_group "Bob's Team"
|
||||
|
||||
groups_page.delete_group! "Bob's Team"
|
||||
|
||||
expect(page).to have_selector('.flash.info', text: I18n.t(:notice_deletion_scheduled))
|
||||
expect(groups_page).to have_group "Bob's Team"
|
||||
|
||||
perform_enqueued_jobs
|
||||
|
||||
groups_page.visit!
|
||||
expect(groups_page).not_to have_group "Bob's Team"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,7 +78,7 @@ describe Group, type: :model do
|
||||
|
||||
puts "Destroying group ..."
|
||||
start = Time.now.to_i
|
||||
group.destroy
|
||||
Principals::DeleteJob.perform_now group
|
||||
@seconds = Time.now.to_i - start
|
||||
|
||||
puts "Destroyed group in #{@seconds} seconds"
|
||||
|
||||
@@ -30,8 +30,6 @@ require 'spec_helper'
|
||||
require_relative '../support/shared/become_member'
|
||||
|
||||
describe Group, type: :model do
|
||||
include BecomeMember
|
||||
|
||||
let(:group) { FactoryBot.create(:group) }
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:watcher) { FactoryBot.create :user }
|
||||
@@ -81,15 +79,7 @@ describe Group, type: :model do
|
||||
|
||||
it 'should roles removed when removing group membership' do
|
||||
expect(user).to be_member_of project
|
||||
member.destroy
|
||||
user.reload
|
||||
project.reload
|
||||
expect(user).not_to be_member_of project
|
||||
end
|
||||
|
||||
it 'should roles removed when removing user from group' do
|
||||
expect(user).to be_member_of project
|
||||
group.destroy
|
||||
Principals::DeleteJob.perform_now group
|
||||
user.reload
|
||||
project.reload
|
||||
expect(user).not_to be_member_of project
|
||||
@@ -117,50 +107,6 @@ describe Group, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
describe 'work packages assigned to the group' do
|
||||
let(:group) { FactoryBot.create(:group, members: [user, watcher]) }
|
||||
before do
|
||||
become_member_with_permissions project, group, [:view_work_packages]
|
||||
package.assigned_to = group
|
||||
|
||||
package.save!
|
||||
end
|
||||
|
||||
it 'should reassign the work package to nobody' do
|
||||
group.destroy
|
||||
|
||||
package.reload
|
||||
|
||||
expect(package.assigned_to).to eq(DeletedUser.first)
|
||||
end
|
||||
|
||||
it 'should update all journals to have the deleted user as assigned' do
|
||||
group.destroy
|
||||
|
||||
package.reload
|
||||
|
||||
expect(package.journals.all? { |j| j.data.assigned_to_id == DeletedUser.first.id }).to be_truthy
|
||||
end
|
||||
|
||||
describe 'watchers' do
|
||||
before do
|
||||
package.watcher_users << watcher
|
||||
end
|
||||
|
||||
context 'with user only in project through group' do
|
||||
it 'should remove the watcher' do
|
||||
group.destroy
|
||||
package.reload
|
||||
project.reload
|
||||
|
||||
expect(package.watchers).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
describe 'group with empty group name' do
|
||||
let(:group) { FactoryBot.build(:group, lastname: '') }
|
||||
|
||||
@@ -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
|
||||
@@ -249,7 +249,7 @@ describe 'API v3 User resource',
|
||||
end
|
||||
|
||||
it 'should lock the account and mark for deletion' do
|
||||
expect(DeleteUserJob)
|
||||
expect(Principals::DeleteJob)
|
||||
.to have_been_enqueued
|
||||
.with(user)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -40,7 +40,7 @@ describe ::PlaceholderUsers::DeleteService, type: :model do
|
||||
shared_examples 'deletes the user' do
|
||||
it do
|
||||
expect(input_user).to receive(:lock!)
|
||||
expect(DeleteUserJob).to receive(:perform_later).with(input_user)
|
||||
expect(Principals::DeleteJob).to receive(:perform_later).with(input_user)
|
||||
expect(subject).to eq true
|
||||
end
|
||||
end
|
||||
@@ -48,7 +48,7 @@ describe ::PlaceholderUsers::DeleteService, type: :model do
|
||||
shared_examples 'does not delete the user' do
|
||||
it do
|
||||
expect(input_user).not_to receive(:lock!)
|
||||
expect(DeleteUserJob).not_to receive(:perform_later)
|
||||
expect(Principals::DeleteJob).not_to receive(:perform_later)
|
||||
expect(subject).to eq false
|
||||
end
|
||||
end
|
||||
@@ -76,7 +76,7 @@ describe ::PlaceholderUsers::DeleteService, type: :model do
|
||||
it 'performs deletion' do
|
||||
actor.run_given do
|
||||
expect(input_user).to receive(:lock!)
|
||||
expect(DeleteUserJob).to receive(:perform_later).with(input_user)
|
||||
expect(Principals::DeleteJob).to receive(:perform_later).with(input_user)
|
||||
expect(subject).to eq true
|
||||
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
|
||||
@@ -39,7 +39,7 @@ describe ::Users::DeleteService, type: :model do
|
||||
shared_examples 'deletes the user' do
|
||||
it do
|
||||
expect(input_user).to receive(:lock!)
|
||||
expect(DeleteUserJob).to receive(:perform_later).with(input_user)
|
||||
expect(Principals::DeleteJob).to receive(:perform_later).with(input_user)
|
||||
expect(subject).to be_success
|
||||
end
|
||||
end
|
||||
@@ -47,7 +47,7 @@ describe ::Users::DeleteService, type: :model do
|
||||
shared_examples 'does not delete the user' do
|
||||
it do
|
||||
expect(input_user).not_to receive(:lock!)
|
||||
expect(DeleteUserJob).not_to receive(:perform_later)
|
||||
expect(Principals::DeleteJob).not_to receive(:perform_later)
|
||||
expect(subject).not_to be_success
|
||||
end
|
||||
end
|
||||
@@ -75,7 +75,7 @@ describe ::Users::DeleteService, type: :model do
|
||||
it 'performs deletion' do
|
||||
actor.run_given do
|
||||
expect(input_user).to receive(:lock!)
|
||||
expect(DeleteUserJob).to receive(:perform_later).with(input_user)
|
||||
expect(Principals::DeleteJob).to receive(:perform_later).with(input_user)
|
||||
expect(subject).to be_success
|
||||
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
|
||||
Reference in New Issue
Block a user