Files
openproject/spec/workers/principals/delete_job_integration_spec.rb

634 lines
18 KiB
Ruby

# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe Principals::DeleteJob, type: :model do
subject(:job) { described_class.perform_now(principal) }
shared_let(:project) { create(:project) }
shared_let(:deleted_user) do
create(:deleted_user)
end
let(:principal) do
create(:user)
end
let(:member) do
create(:member,
principal:,
project:,
roles: [role])
end
shared_let(:role) do
create(:project_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
create(:work_package,
assigned_to: principal,
responsible: principal)
end
before do
work_package
principal.update_column(:status, User.statuses[:deleted])
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) { create(:labor_budget_item, principal:) }
before do
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) { create(:work_package) }
let(:entry) do
create(:cost_entry,
user: principal,
project: work_package.project,
units: 100.0,
spent_on: Time.zone.today,
entity: work_package,
comments: "")
end
before do
create(:member,
project: work_package.project,
user: principal,
roles: [build(:project_role)])
entry
job
entry.reload
end
it { expect(entry.user_id).to eq(deleted_user.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 "work package member handling" do
let(:work_package) { create(:work_package, project:) }
let(:work_package_member) do
create(:work_package_member,
principal:,
project:,
work_package:,
roles: [role])
end
before do
work_package_member
job
end
it "removes that work package member" do
expect(Member.find_by(id: work_package_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 work_package" do
expect(WorkPackage.find_by(id: work_package.id)).to eq(work_package)
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
build(:hourly_rate,
user: principal,
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) { create(:news, 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 "rss 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 "notification handling" do
let(:notification) do
create(:notification, recipient: principal)
end
before do
notification
job
end
it { expect(Notification.find_by(id: notification.id)).to be_nil }
end
shared_examples_for "private query handling" do
let!(:query) do
create(:private_query, user: principal, views: create_list(:view_work_packages_table, 1))
end
before do
job
end
it { expect(Query.find_by(id: query.id)).to be_nil }
end
shared_examples_for "persisted view and query handling" do
let!(:private_view) { PersistedView.create!(name: "Private", public: false, principal:) }
let!(:public_view) { PersistedView.create!(name: "Public", public: true, principal:) }
# Query shared between the principal's private view and another user's public view —
# must survive the destruction of the private view.
let!(:other_user) { create(:user) }
let!(:shared_query) { UserQuery.create!(name: "Shared") }
let!(:shared_private_view) do
PersistedView.create!(name: "Shared-private", public: false, principal:, query: shared_query)
end
let!(:other_public_view) do
PersistedView.create!(name: "Other-public", public: true, principal: other_user, query: shared_query)
end
# Query referenced only from the principal's private view — should be destroyed along with the view.
let!(:orphaned_query) { UserQuery.create!(name: "Orphaned", principal:) }
let!(:orphaning_view) do
PersistedView.create!(name: "Orphaning-private", public: false, principal:, query: orphaned_query)
end
# Public query owned by the principal — kept, principal_id nullified.
let!(:kept_user_query) { UserQuery.create!(name: "Public-owned", principal:) }
let!(:kept_public_view) do
PersistedView.create!(name: "Keeps-query", public: true, principal: other_user, query: kept_user_query)
end
# Manual entries pointing at the principal — must be deleted.
let!(:manual_entry_user) do
OrderedPersistedQueryEntity.create!(persisted_query: shared_query, entity: principal, position: 1)
end
let!(:manual_entry_other) do
OrderedPersistedQueryEntity.create!(persisted_query: shared_query, entity: other_user, position: 2)
end
before { job }
it "deletes private views owned by the principal" do
expect(PersistedView.find_by(id: private_view.id)).to be_nil
expect(PersistedView.find_by(id: shared_private_view.id)).to be_nil
expect(PersistedView.find_by(id: orphaning_view.id)).to be_nil
end
it "keeps public views but nullifies their principal_id" do
expect(public_view.reload.principal_id).to be_nil
end
it "destroys queries that are no longer referenced by any public view" do
expect(PersistedQuery.find_by(id: orphaned_query.id)).to be_nil
end
it "keeps queries still referenced by another public view" do
expect(PersistedQuery.find_by(id: shared_query.id)).to eq(shared_query)
end
it "nullifies principal_id on surviving queries" do
expect(kept_user_query.reload.principal_id).to be_nil
end
it "deletes ordered entries that pointed at the principal" do
expect(OrderedPersistedQueryEntity.find_by(id: manual_entry_user.id)).to be_nil
end
it "keeps ordered entries that point at other users" do
expect(OrderedPersistedQueryEntity.find_by(id: manual_entry_other.id)).to eq(manual_entry_other)
end
end
shared_examples_for "backup token handling" do
let!(:backup_token) do
create(:backup_token, user: principal)
end
let!(:invitation_token) do
create(:invitation_token, user: principal)
end
before do
job
end
it { expect(Token::Base.where(user_id: principal.id)).to be_empty }
end
shared_examples_for "issue category handling" do
let(:category) do
create(:category,
assigned_to: principal,
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) { create(:private_cost_query, user: principal) }
it "removes the query" do
job
expect(CostQuery.find_by(id: query.id)).to be_nil
end
end
shared_examples_for "project query handling" do
let!(:query) { create(:project_query, user: principal) }
it "removes the query" do
job
expect(ProjectQuery.find_by(id: query.id)).to be_nil
end
end
shared_examples_for "working hours handling" do
let!(:working_hours) { create(:user_working_hours, user: principal) }
it "removes the working hours" do
job
expect(UserWorkingHours.find_by(id: working_hours.id)).to be_nil
end
end
shared_examples_for "non working times handling" do
let!(:non_working_time) { create(:user_non_working_time, user: principal) }
it "removes the non working times" do
job
expect(UserNonWorkingTime.find_by(id: non_working_time.id)).to be_nil
end
end
shared_examples_for "public cost_query handling" do
let!(:query) { 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) { create(:cost_query) }
let(:other_user) { 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 its 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 its 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 its 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_behaves_like "public query rewriting"
end
describe "with the query has a author_id filter" do
let(:filter) { CostQuery::Filter::AuthorId }
it_behaves_like "public query rewriting"
end
describe "with the query has a assigned_to_id filter" do
let(:filter) { CostQuery::Filter::AssignedToId }
it_behaves_like "public query rewriting"
end
describe "with the query has an responsible_id filter" do
let(:filter) { CostQuery::Filter::ResponsibleId }
it_behaves_like "public query rewriting"
end
end
shared_examples_for "mention rewriting" do
let(:text) do
<<~TEXT
<mention class="mention"
data-id="#{principal.id}"
data-type="user"
data-text="@#{principal.name}">
@#{principal.name}
</mention>
TEXT
end
let(:expected_text) do
<<~TEXT.squish
<mention class="mention"
data-id="#{deleted_user.id}"
data-type="user"
data-text="@#{deleted_user.name}">@#{deleted_user.name}</mention>
TEXT
end
let!(:work_package) { create(:work_package, description: text) }
before do
job
end
it "rewrites the mentioning in the text" do
expect(work_package.reload.description)
.to include expected_text
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 "rss token handling"
it_behaves_like "backup token handling"
it_behaves_like "notification handling"
it_behaves_like "private query handling"
it_behaves_like "persisted view and 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"
it_behaves_like "project query handling"
it_behaves_like "mention rewriting"
it_behaves_like "working hours handling"
it_behaves_like "non working times handling"
describe "favorites" do
before do
project.add_favoriting_user(principal)
job
end
it "removes the assigned_to association to the principal" do
expect(project.favoriting_users.reload).to be_empty
end
end
end
context "with a group" do
let(:principal) { 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"
it_behaves_like "mention rewriting"
context "with user only in project through group" do
let(:user) do
create(:user)
end
let(:group_members) { [user] }
let(:watched) { create(:news, project:) }
let(:watch) do
Watcher.create(user:,
watchable: watched)
end
it "removes the watcher" do
job
expect(watched.watchers.reload).to be_empty
end
end
end
context "with a service account" do
describe "service account association" do
let(:principal) { create(:service_account, service:) }
let(:service) { create(:oauth_application) }
before do
principal.save!
end
it "deletes the service account association" do
expect { job }.to change(ServiceAccountAssociation, :count).from(1).to(0)
end
it "does not delete the service associated to the service account" do
expect { job }.not_to change(Doorkeeper::Application, :count)
end
end
end
context "with a placeholder user" do
let(:principal) { create(:placeholder_user) }
it_behaves_like "removes the principal"
it_behaves_like "work_package handling"
end
end
end