OpenProject is the leading open source project management software.
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.
openproject/modules/reporting/spec/models/cost_query/chaining_spec.rb

310 lines
10 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 COPYRIGHT and LICENSE files for more details.
#++
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
describe CostQuery, type: :model, reporting_query_helper: true do
let(:project) { FactoryBot.create(:project) }
minimal_query
describe '#chain' do
before do
# FIXME: is there a better way to load all filter and groups?
14 years ago
CostQuery::Filter.all && CostQuery::GroupBy.all
CostQuery.chain_initializer.clear
end
after(:all) do
CostQuery.chain_initializer.clear
end
it "should contain NoFilter" do
expect(@query.chain).to be_a(CostQuery::Filter::NoFilter)
end
it "should keep NoFilter at bottom" do
@query.filter :project_id
expect(@query.chain.bottom).to be_a(CostQuery::Filter::NoFilter)
expect(@query.chain.top).not_to be_a(CostQuery::Filter::NoFilter)
end
it "should remember it's correct parent" do
@query.group_by :project_id
@query.filter :project_id
expect(@query.chain.top.child.child.parent).to eq(@query.chain.top.child)
end
it "should place filter after a group_by" do
@query.group_by :project_id
expect(@query.chain.bottom.parent).to be_a(CostQuery::GroupBy::ProjectId)
expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId)
@query.filter :project_id
expect(@query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId)
expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId)
end
it "should place rows in front of columns when adding a column first" do
@query.column :project_id
expect(@query.chain.bottom.parent.type).to eq(:column)
expect(@query.chain.top.type).to eq(:column)
12 years ago
@query.row :project_id
expect(@query.chain.bottom.parent.type).to eq(:column)
expect(@query.chain.top.type).to eq(:row)
end
it "should place rows in front of filters" do
@query.row :project_id
expect(@query.chain.bottom.parent.type).to eq(:row)
expect(@query.chain.top.type).to eq(:row)
@query.filter :project_id
expect(@query.chain.bottom.parent).to be_a(CostQuery::Filter::ProjectId)
expect(@query.chain.top).to be_a(CostQuery::GroupBy::ProjectId)
expect(@query.chain.top.type).to eq(:row)
end
it "should return all filters, including the NoFilter" do
@query.filter :project_id
@query.group_by :project_id
expect(@query.filters.size).to eq(2)
expect(@query.filters.map { |f| f.class.underscore_name }).to include "project_id"
end
it "should return all group_bys" do
@query.filter :project_id
@query.group_by :project_id
expect(@query.group_bys.size).to eq(1)
expect(@query.group_bys.map { |g| g.class.underscore_name }).to include "project_id"
end
it "should initialize the chain through a block" do
class TestFilter < Report::Filter::Base
def self.engine
CostQuery
end
end
TestFilter.send(:initialize_query_with) { |query| query.filter(:project_id, value: project.id) }
@query.build_new_chain
expect(@query.filters.map { |f| f.class.underscore_name }).to include "project_id"
expect(@query.filters.detect { |f| f.class.underscore_name == "project_id" }.values).to eq(Array(project.id))
end
context "store and load" do
before do
@query.filter :project_id, value: project.id
@query.filter :cost_type_id, value: CostQuery::Filter::CostTypeId.available_values.first
@query.filter :category_id, value: CostQuery::Filter::CategoryId.available_values.first
@query.group_by :activity_id
Feature/remove timelog (#8557) * rename costs, introduce budgets * move files from costs to budgets * rename cost_object to budget * remove unused code * move hook - should be turned into standard code in the long run * move type attributes change over to budgets * move patch to work_package proper * move budget menu item up * combine reporting, time and cost module * remove rails based time_entries & reports code * rename cost object filter * adapt menu spec expectations * use cost project module name in administration * include timeline labels in migration * properly place budget linking method * fix permitted params * remove outdated routing spec * adapt budget request specs * ensure order of descendent updates * remove outdated specs * fix checking for reporting to be enabled * fix displaying spent units * fix time entries activity event url * reenable current rate tab * fix path on budget page * allow bulk editing of budgets only in one project scenario * fix sanitizing reference in controller * include module required for format_date * fix reference to correct units from work package spent units * linting * remove outdated spec * remove outdated views and permission references * remove acts_as_event from time_entries There is no atom link for time entries * remove acts_as_event from projects There are no atom links for projects * introduce budget filter for cost reports * remove actions added to removed controller * move time entries to the costs module * factor in view_own permission when calculating time entry visibility * linting * move mounting of time entries * include budgets into api v3 documentation
4 years ago
@query.group_by :budget_id
@query.group_by :cost_type_id
@new_query = CostQuery.deserialize(@query.serialize)
end
14 years ago
it "should serialize the chain correctly" do
%i[filters group_bys].each do |type|
14 years ago
@query.send(type).each do |chainable|
expect(@query.serialize[type].collect { |c| c[0] }).to include chainable.class.name.demodulize
14 years ago
end
end
end
it "should deserialize a serialized query correctly" do
expect(@new_query.serialize).to eq(@query.serialize)
end
it "should keep the order of group bys" do
@query.group_bys.each_with_index do |group_by, index|
# check for order
@new_query.group_bys.each_with_index do |g, ix|
if g.instance_of?(group_by.class)
expect(ix).to eq(index)
end
end
end
end
it "should keep the right filter values" do
@query.filters.each_with_index do |filter, _index|
# check for presence
expect(@new_query.filters.any? do |f|
f.instance_of?(filter.class) && (filter.respond_to?(:values) ? f.values == filter.values : true)
end).to be_truthy
end
end
end
end
describe Report::Chainable do
describe '#top' do
before { @chain = Report::Chainable.new }
it "returns for an one element long chain that chain as top" do
expect(@chain.top).to eq(@chain)
expect(@chain).to be_top
end
it "does not keep the old top when prepending elements" do
Report::Chainable.new @chain
expect(@chain.top).not_to eq(@chain)
expect(@chain).not_to be_top
end
it "sets new top when prepending elements" do
current = @chain
10.times do
old = current
current = Report::Chainable.new(current)
expect(old.top).to eq(current)
expect(@chain.top).to eq(current)
end
end
end
describe '#inherited_attribute' do
before do
@a = Class.new Report::Chainable
@a.inherited_attribute :foo, default: 42
@b = Class.new @a
@c = Class.new @a
@d = Class.new @b
end
it 'takes default argument' do
expect(@a.foo).to eq(42)
expect(@b.foo).to eq(42)
expect(@c.foo).to eq(42)
expect(@d.foo).to eq(42)
end
it 'inherits values' do
@a.foo 1337
expect(@d.foo).to eq(1337)
end
it 'does not change values of parents and akin' do
@b.foo 1337
expect(@a.foo).not_to eq(1337)
expect(@c.foo).not_to eq(1337)
end
it 'is able to map values' do
@a.inherited_attribute :bar, map: proc { |x| x * 2 }
@a.bar 21
expect(@a.bar).to eq(42)
end
describe '#list' do
it "merges lists" do
@a.inherited_attribute :bar, list: true
@a.bar 1; @b.bar 2; @d.bar 3, 4
expect(@a.bar).to eq([1])
expect(@b.bar.sort).to eq([1, 2])
expect(@c.bar.sort).to eq([1])
expect(@d.bar.sort).to eq([1, 2, 3, 4])
end
it "is able to map lists" do
@a.inherited_attribute :bar, list: true, map: :to_s
@a.bar 1; @b.bar 1; @d.bar 1
expect(@a.bar).to eq(%w[1])
expect(@b.bar).to eq(%w[1 1])
expect(@c.bar).to eq(%w[1])
expect(@d.bar).to eq(%w[1 1 1])
end
it "is able to produce uniq lists" do
@a.inherited_attribute :bar, list: true, uniq: true
@a.bar 1, 1, 2
@b.bar 2, 3
expect(@b.bar.sort).to eq([1, 2, 3])
end
it "keeps old entries" do
@a.inherited_attribute :bar, list: true
@a.bar 1
@a.bar 2
expect(@a.bar.sort).to eq([1, 2])
end
end
end
describe '#display' do
it "should give display? == false when a filter says dont_display!" do
class TestFilter < Report::Filter::Base
dont_display!
end
expect(TestFilter.display?).to be false
Object.send(:remove_const, :TestFilter)
end
it "should give display? == true when a filter doesn't specify it's visibility" do
class TestFilter < Report::Filter::Base
end
expect(TestFilter.display?).to be true
Object.send(:remove_const, :TestFilter)
end
it "should give display? == true when a filter says display!" do
class TestFilter < Report::Filter::Base
display!
end
expect(TestFilter.display?).to be true
Object.send(:remove_const, :TestFilter)
end
end
describe '#selectable' do
it "should give selectable? == false when a filter says not_selectable!" do
class TestFilter < Report::Filter::Base
not_selectable!
end
expect(TestFilter.selectable?).to be false
Object.send(:remove_const, :TestFilter)
end
it "should give selectable? == true when a filter doesn't specify it's selectability" do
class TestFilter < Report::Filter::Base
end
expect(TestFilter.selectable?).to be true
Object.send(:remove_const, :TestFilter)
end
it "should give selectable? == true when a filter says selectable!" do
class TestFilter < Report::Filter::Base
selectable!
end
expect(TestFilter.selectable?).to be true
Object.send(:remove_const, :TestFilter)
end
end
end
end