kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
693 lines
21 KiB
693 lines
21 KiB
#-- 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 WorkPackages::BulkController, type: :controller, with_settings: { journal_aggregation_time_minutes: 0 } do
|
|
let(:user) { FactoryBot.create(:user) }
|
|
let(:user2) { FactoryBot.create(:user) }
|
|
let(:custom_field_value) { '125' }
|
|
let(:custom_field_1) do
|
|
FactoryBot.create(:work_package_custom_field,
|
|
field_format: 'string',
|
|
is_for_all: true)
|
|
end
|
|
let(:custom_field_2) { FactoryBot.create(:work_package_custom_field) }
|
|
let(:custom_field_user) { FactoryBot.create(:user_issue_custom_field) }
|
|
let(:status) { FactoryBot.create(:status) }
|
|
let(:type) do
|
|
FactoryBot.create(:type_standard,
|
|
custom_fields: [custom_field_1, custom_field_2, custom_field_user])
|
|
end
|
|
let(:project_1) do
|
|
FactoryBot.create(:project,
|
|
types: [type],
|
|
work_package_custom_fields: [custom_field_2])
|
|
end
|
|
let(:project_2) do
|
|
FactoryBot.create(:project,
|
|
types: [type])
|
|
end
|
|
let(:role) do
|
|
FactoryBot.create(:role,
|
|
permissions: %i[edit_work_packages
|
|
view_work_packages
|
|
manage_subtasks
|
|
assign_versions])
|
|
end
|
|
let(:member1_p1) do
|
|
FactoryBot.create(:member,
|
|
project: project_1,
|
|
principal: user,
|
|
roles: [role])
|
|
end
|
|
let(:member2_p1) do
|
|
FactoryBot.create(:member,
|
|
project: project_1,
|
|
principal: user2,
|
|
roles: [role])
|
|
end
|
|
let(:member1_p2) do
|
|
FactoryBot.create(:member,
|
|
project: project_2,
|
|
principal: user,
|
|
roles: [role])
|
|
end
|
|
let(:work_package_1) do
|
|
FactoryBot.create(:work_package,
|
|
author: user,
|
|
assigned_to: user,
|
|
responsible: user2,
|
|
type: type,
|
|
status: status,
|
|
custom_field_values: { custom_field_1.id => custom_field_value },
|
|
project: project_1)
|
|
end
|
|
let(:work_package_2) do
|
|
FactoryBot.create(:work_package,
|
|
author: user,
|
|
assigned_to: user,
|
|
responsible: user2,
|
|
type: type,
|
|
status: status,
|
|
custom_field_values: { custom_field_1.id => custom_field_value },
|
|
project: project_1)
|
|
end
|
|
let(:work_package_3) do
|
|
FactoryBot.create(:work_package,
|
|
author: user,
|
|
type: type,
|
|
status: status,
|
|
custom_field_values: { custom_field_1.id => custom_field_value },
|
|
project: project_2)
|
|
end
|
|
|
|
let(:stub_work_package) { FactoryBot.build_stubbed(:work_package) }
|
|
|
|
before do
|
|
custom_field_1
|
|
member1_p1
|
|
member2_p1
|
|
|
|
allow(User).to receive(:current).and_return user
|
|
end
|
|
|
|
describe '#edit' do
|
|
shared_examples_for :response do
|
|
subject { response }
|
|
|
|
it { is_expected.to be_successful }
|
|
|
|
it { is_expected.to render_template('edit') }
|
|
end
|
|
|
|
context 'same project' do
|
|
before { get :edit, params: { ids: [work_package_1.id, work_package_2.id] } }
|
|
|
|
it_behaves_like :response
|
|
|
|
describe '#view' do
|
|
render_views
|
|
|
|
subject { response }
|
|
|
|
describe '#parent' do
|
|
it { assert_select 'input', attributes: { name: 'work_package[parent_id]' } }
|
|
end
|
|
|
|
context 'custom_field' do
|
|
describe '#type' do
|
|
it { assert_select 'input', attributes: { name: "work_package[custom_field_values][#{custom_field_1.id}]" } }
|
|
end
|
|
|
|
describe '#project' do
|
|
it { assert_select 'select', attributes: { name: "work_package[custom_field_values][#{custom_field_2.id}]" } }
|
|
end
|
|
|
|
describe '#user' do
|
|
it { assert_select 'select', attributes: { name: "work_package[custom_field_values][#{custom_field_user.id}]" } }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'different projects' do
|
|
before do
|
|
member1_p2
|
|
|
|
get :edit, params: { ids: [work_package_1.id, work_package_2.id, work_package_3.id] }
|
|
end
|
|
|
|
it_behaves_like :response
|
|
|
|
describe '#view' do
|
|
render_views
|
|
|
|
subject { response }
|
|
|
|
describe '#parent' do
|
|
it { assert_select 'input', { attributes: { name: 'work_package[parent_id]' } }, false }
|
|
end
|
|
|
|
context 'custom_field' do
|
|
describe '#type' do
|
|
it { assert_select 'input', attributes: { name: "work_package[custom_field_values][#{custom_field_1.id}]" } }
|
|
end
|
|
|
|
describe '#project' do
|
|
it {
|
|
assert_select 'select', { attributes: { name: "work_package[custom_field_values][#{custom_field_2.id}]" } }, false
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update' do
|
|
let(:work_package_ids) { [work_package_1.id, work_package_2.id] }
|
|
let(:work_packages) { WorkPackage.where(id: work_package_ids) }
|
|
let(:priority) { FactoryBot.create(:priority_immediate) }
|
|
let(:group_id) { '' }
|
|
let(:responsible_id) { '' }
|
|
|
|
describe '#redirect' do
|
|
context 'in host' do
|
|
let(:url) { '/work_packages' }
|
|
|
|
before { put :update, params: { ids: work_package_ids, back_url: url } }
|
|
|
|
subject { response }
|
|
|
|
it { is_expected.to be_redirect }
|
|
|
|
it { is_expected.to redirect_to(url) }
|
|
end
|
|
|
|
context 'of host' do
|
|
let(:url) { 'http://google.com' }
|
|
|
|
before { put :update, params: { ids: work_package_ids, back_url: url } }
|
|
|
|
subject { response }
|
|
|
|
it { is_expected.to be_redirect }
|
|
|
|
it { is_expected.to redirect_to(project_work_packages_path(project_1)) }
|
|
end
|
|
end
|
|
|
|
context 'when updating two work packages with differing whitelisted params' do
|
|
let!(:work_package_ids) { [work_package_1.id, work_package_3.id] }
|
|
|
|
let!(:role_with_permission_to_add_watchers) do
|
|
FactoryBot.create(:role, permissions: role.permissions + [:add_work_package_watchers])
|
|
end
|
|
let!(:other_user) { FactoryBot.create :user }
|
|
|
|
let!(:other_member_1) do
|
|
FactoryBot.create(:member,
|
|
project: project_1,
|
|
principal: other_user,
|
|
roles: [role_with_permission_to_add_watchers])
|
|
end
|
|
let!(:other_member_2) do
|
|
FactoryBot.create(:member,
|
|
project: project_2,
|
|
principal: other_user,
|
|
roles: [role])
|
|
end
|
|
|
|
let(:description) { 'Text' }
|
|
let(:work_package_params) do
|
|
{ description: description, watcher_user_ids: [user.id] }
|
|
end
|
|
|
|
before do
|
|
# create user memberships to allow the user to watch work packages
|
|
member1_p1
|
|
member1_p2
|
|
# let other_user perform the bulk update
|
|
allow(User).to receive(:current).and_return other_user
|
|
put :update, params: { ids: work_package_ids, work_package: work_package_params }
|
|
end
|
|
|
|
it 'updates the description if whitelisted' do
|
|
expect(work_package_1.reload.description).to eq(description)
|
|
expect(work_package_3.reload.description).to eq(description)
|
|
end
|
|
|
|
it 'updates the watchers if the watcher user ids are whitelisted' do
|
|
expect(work_package_1.reload.watcher_users).to include user
|
|
end
|
|
|
|
it 'does not update the watchers if the watcher user ids are not whitelisted' do
|
|
expect(work_package_3.reload.watcher_users).not_to include user
|
|
end
|
|
end
|
|
|
|
shared_context 'update_request' do
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
notes: 'Bulk editing',
|
|
work_package: { priority_id: priority.id,
|
|
assigned_to_id: group_id,
|
|
responsible_id: responsible_id,
|
|
send_notification: send_notification }
|
|
}
|
|
end
|
|
end
|
|
|
|
shared_examples_for :delivered do
|
|
subject { ActionMailer::Base.deliveries.size }
|
|
|
|
it { delivery_size }
|
|
end
|
|
|
|
context 'with notification' do
|
|
let(:send_notification) { '1' }
|
|
let(:delivery_size) { 2 }
|
|
|
|
shared_examples_for 'updated work package' do
|
|
describe '#priority' do
|
|
subject { WorkPackage.where(priority_id: priority.id).map(&:id) }
|
|
|
|
it { is_expected.to match_array(work_package_ids) }
|
|
end
|
|
|
|
describe '#custom_fields' do
|
|
let(:result) { [custom_field_value] }
|
|
|
|
subject do
|
|
WorkPackage.where(id: work_package_ids)
|
|
.map { |w| w.custom_value_for(custom_field_1.id).value }
|
|
.uniq
|
|
end
|
|
|
|
it { is_expected.to match_array(result) }
|
|
end
|
|
|
|
describe '#journal' do
|
|
describe '#notes' do
|
|
let(:result) { ['Bulk editing'] }
|
|
|
|
subject do
|
|
WorkPackage.where(id: work_package_ids)
|
|
.map { |w| w.last_journal.notes }
|
|
.uniq
|
|
end
|
|
|
|
it { is_expected.to match_array(result) }
|
|
end
|
|
|
|
describe '#details' do
|
|
let(:result) { [1] }
|
|
|
|
subject do
|
|
WorkPackage.where(id: work_package_ids)
|
|
.map { |w| w.last_journal.details.size }
|
|
.uniq
|
|
end
|
|
|
|
it { is_expected.to match_array(result) }
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'single project' do
|
|
include_context 'update_request'
|
|
|
|
it { expect(response.response_code).to eq(302) }
|
|
|
|
it_behaves_like :delivered
|
|
|
|
it_behaves_like 'updated work package'
|
|
end
|
|
|
|
context 'different projects' do
|
|
let(:work_package_ids) { [work_package_1.id, work_package_2.id, work_package_3.id] }
|
|
|
|
context 'with permission' do
|
|
before { member1_p2 }
|
|
|
|
include_context 'update_request'
|
|
|
|
it { expect(response.response_code).to eq(302) }
|
|
|
|
it_behaves_like :delivered
|
|
|
|
it_behaves_like 'updated work package'
|
|
end
|
|
|
|
context 'w/o permission' do
|
|
include_context 'update_request'
|
|
|
|
it { expect(response.response_code).to eq(403) }
|
|
|
|
describe '#journal' do
|
|
subject { Journal.count }
|
|
|
|
it { is_expected.to eq(work_package_ids.count) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#properties' do
|
|
describe '#groups' do
|
|
let(:group) { FactoryBot.create(:group) }
|
|
let(:group_id) { group.id }
|
|
subject { work_packages.map(&:assigned_to_id).uniq }
|
|
|
|
context 'allowed' do
|
|
let!(:member_group_p1) do
|
|
FactoryBot.create(:member,
|
|
project: project_1,
|
|
principal: group,
|
|
roles: [role])
|
|
end
|
|
|
|
include_context 'update_request'
|
|
it 'does succeed' do
|
|
expect(flash[:error]).to be_nil
|
|
expect(subject).to match_array [group.id]
|
|
end
|
|
end
|
|
|
|
context 'not allowed' do
|
|
include_context 'update_request'
|
|
|
|
it 'does not succeed' do
|
|
expect(assigns[:bulk_errors].keys).to match_array(work_package_ids)
|
|
expect(subject).to match_array [user.id]
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#responsible' do
|
|
let(:responsible_id) { user.id }
|
|
|
|
include_context 'update_request'
|
|
|
|
subject { work_packages.map(&:responsible_id).uniq }
|
|
|
|
it { is_expected.to match_array [responsible_id] }
|
|
end
|
|
|
|
describe '#status' do
|
|
let(:closed_status) { FactoryBot.create(:closed_status) }
|
|
let(:workflow) do
|
|
FactoryBot.create(:workflow,
|
|
role: role,
|
|
type_id: type.id,
|
|
old_status: status,
|
|
new_status: closed_status)
|
|
end
|
|
|
|
before do
|
|
workflow
|
|
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { status_id: closed_status.id }
|
|
}
|
|
end
|
|
|
|
subject { work_packages.map(&:status_id).uniq }
|
|
|
|
it { is_expected.to match_array [closed_status.id] }
|
|
end
|
|
|
|
describe '#parent' do
|
|
let(:parent) do
|
|
FactoryBot.create(:work_package,
|
|
author: user,
|
|
project: project_1)
|
|
end
|
|
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { parent_id: parent.id }
|
|
}
|
|
end
|
|
|
|
subject { work_packages.map(&:parent_id).uniq }
|
|
|
|
it { is_expected.to match_array [parent.id] }
|
|
end
|
|
|
|
describe '#custom_fields' do
|
|
let(:result) { '777' }
|
|
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: {
|
|
custom_field_values: { custom_field_1.id.to_s => result }
|
|
}
|
|
}
|
|
end
|
|
|
|
subject do
|
|
work_packages.map { |w| w.custom_value_for(custom_field_1.id).value }
|
|
.uniq
|
|
end
|
|
|
|
it { is_expected.to match_array [result] }
|
|
end
|
|
|
|
describe '#unassign' do
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { assigned_to_id: 'none' }
|
|
}
|
|
end
|
|
|
|
subject { work_packages.map(&:assigned_to_id).uniq }
|
|
|
|
it { is_expected.to match_array [nil] }
|
|
end
|
|
|
|
describe '#delete_responsible' do
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { responsible_id: 'none' }
|
|
}
|
|
end
|
|
|
|
subject { work_packages.map(&:responsible_id).uniq }
|
|
|
|
it { is_expected.to match_array [nil] }
|
|
end
|
|
|
|
describe '#version' do
|
|
describe 'set version_id attribute to some version' do
|
|
let(:version) do
|
|
FactoryBot.create(:version,
|
|
status: 'open',
|
|
sharing: 'tree',
|
|
project: subproject)
|
|
end
|
|
let(:subproject) do
|
|
FactoryBot.create(:project,
|
|
parent: project_1,
|
|
types: [type])
|
|
end
|
|
|
|
before do
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { version_id: version.id.to_s }
|
|
}
|
|
end
|
|
|
|
subject { response }
|
|
|
|
it { is_expected.to be_redirect }
|
|
|
|
describe '#work_package' do
|
|
describe '#version' do
|
|
subject { work_packages.map(&:version_id).uniq }
|
|
|
|
it { is_expected.to match_array [version.id] }
|
|
end
|
|
|
|
describe '#project' do
|
|
subject { work_packages.map(&:project_id).uniq }
|
|
|
|
it { is_expected.not_to match_array [subproject.id] }
|
|
end
|
|
end
|
|
end
|
|
describe 'set version_id to nil' do
|
|
before do
|
|
# 'none' is a magic value, setting version_id to nil
|
|
# will make the controller ignore that param
|
|
put :update,
|
|
params: {
|
|
ids: work_package_ids,
|
|
work_package: { version_id: 'none' }
|
|
}
|
|
end
|
|
describe '#work_package' do
|
|
describe '#version' do
|
|
subject { work_packages.map(&:version_id).uniq }
|
|
|
|
it { is_expected.to eq([nil]) }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'w/o notification' do
|
|
let(:send_notification) { '0' }
|
|
|
|
describe '#delivery' do
|
|
include_context 'update_request'
|
|
|
|
it { expect(response.response_code).to eq(302) }
|
|
|
|
let(:delivery_size) { 0 }
|
|
|
|
it_behaves_like :delivered
|
|
end
|
|
end
|
|
|
|
describe 'updating two children with dates to a new parent (Regression #28670)' do
|
|
let(:task1) do
|
|
FactoryBot.create :work_package,
|
|
project: project_1,
|
|
start_date: Date.today - 5.days,
|
|
due_date: Date.today
|
|
end
|
|
|
|
let(:task2) do
|
|
FactoryBot.create :work_package,
|
|
project: project_1,
|
|
start_date: Date.today - 2.days,
|
|
due_date: Date.today + 1.days
|
|
end
|
|
|
|
let(:new_parent) do
|
|
FactoryBot.create :work_package, project: project_1
|
|
end
|
|
|
|
before do
|
|
expect(new_parent.start_date).to be_nil
|
|
expect(new_parent.due_date).to be_nil
|
|
|
|
put :update,
|
|
params: {
|
|
ids: [task1.id, task2.id],
|
|
notes: 'Bulk editing',
|
|
work_package: { parent_id: new_parent.id }
|
|
}
|
|
end
|
|
|
|
it 'should update the parent dates as well' do
|
|
expect(response.response_code).to eq(302)
|
|
|
|
task1.reload
|
|
task2.reload
|
|
new_parent.reload
|
|
|
|
expect(task1.parent_id).to eq(new_parent.id)
|
|
expect(task2.parent_id).to eq(new_parent.id)
|
|
|
|
expect(new_parent.start_date).to eq Date.today - 5.days
|
|
expect(new_parent.due_date).to eq Date.today + 1.days
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#destroy' do
|
|
let(:params) { { 'ids' => '1', 'to_do' => 'blubs' } }
|
|
let(:service) { double('destroy wp service') }
|
|
|
|
before do
|
|
expect(controller).to receive(:find_work_packages) do
|
|
controller.instance_variable_set(:@work_packages, [stub_work_package])
|
|
end
|
|
|
|
expect(controller).to receive(:authorize)
|
|
end
|
|
|
|
describe 'w/ the cleanup being successful' do
|
|
before do
|
|
expect(stub_work_package).to receive(:reload).and_return(stub_work_package)
|
|
|
|
allow(WorkPackages::DeleteService)
|
|
.to receive(:new)
|
|
.with(user: user, model: stub_work_package)
|
|
.and_return(service)
|
|
|
|
expect(service)
|
|
.to receive(:call)
|
|
|
|
expect(WorkPackage)
|
|
.to receive(:cleanup_associated_before_destructing_if_required)
|
|
.with([stub_work_package], user, params['to_do']).and_return true
|
|
|
|
as_logged_in_user(user) do
|
|
delete :destroy, params: params
|
|
end
|
|
end
|
|
|
|
it 'should redirect to the project' do
|
|
expect(response).to redirect_to(project_work_packages_path(stub_work_package.project))
|
|
end
|
|
end
|
|
|
|
describe 'w/o the cleanup being successful' do
|
|
before do
|
|
expect(WorkPackage).to receive(:cleanup_associated_before_destructing_if_required).with([stub_work_package], user,
|
|
params['to_do']).and_return false
|
|
|
|
as_logged_in_user(user) do
|
|
delete :destroy, params: params
|
|
end
|
|
end
|
|
|
|
it 'should redirect to the project' do
|
|
expect(response).to render_template('destroy')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|