Merge remote-tracking branch 'origin/release/10.6' into dev

pull/8379/head
Oliver Günther 5 years ago
commit f832efa52c
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 2
      Gemfile
  2. 112
      Gemfile.lock
  3. 49
      app/models/queries/work_packages/filter/subproject_filter.rb
  4. 5
      app/services/projects/create_service.rb
  5. 4
      app/services/projects/update_service.rb
  6. 5
      frontend/src/app/modules/fields/changeset/resource-changeset.ts
  7. 4
      lib/open_project/events.rb
  8. 4
      modules/auth_plugins/lib/open_project/plugins/auth_plugin.rb
  9. 66
      modules/auth_saml/lib/open_project/auth_saml/engine.rb
  10. 52
      modules/auth_saml/spec/lib/open_project/auth_saml_spec.rb
  11. 2
      modules/auth_saml/spec/spec_helper.rb
  12. 4
      modules/my_page/spec/features/my/time_entries_current_user_spec.rb
  13. 51
      modules/webhooks/app/workers/project_webhook_job.rb
  14. 2
      modules/webhooks/lib/open_project/webhooks/event_resources.rb
  15. 33
      modules/webhooks/lib/open_project/webhooks/event_resources/project.rb
  16. 152
      modules/webhooks/spec/workers/project_webhook_job.rb
  17. 56
      spec/models/queries/work_packages/filter/subproject_filter_spec.rb

@ -34,7 +34,7 @@ gem 'actionpack-xml_parser', '~> 2.0.0'
gem 'activemodel-serializers-xml', '~> 1.0.1'
gem 'activerecord-import', '~> 1.0.2'
gem 'activerecord-session_store', '~> 1.1.0'
gem 'rails', '~> 6.0.2'
gem 'rails', '~> 6.0.3.1'
gem 'responders', '~> 3.0'
gem 'rdoc', '>= 2.4.2'

@ -195,26 +195,26 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.0.3)
actioncable (6.0.2.2)
actionpack (= 6.0.2.2)
actioncable (6.0.3.1)
actionpack (= 6.0.3.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.2.2)
actionpack (= 6.0.2.2)
activejob (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
actionmailbox (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
mail (>= 2.7.1)
actionmailer (6.0.2.2)
actionpack (= 6.0.2.2)
actionview (= 6.0.2.2)
activejob (= 6.0.2.2)
actionmailer (6.0.3.1)
actionpack (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.2.2)
actionview (= 6.0.2.2)
activesupport (= 6.0.2.2)
actionpack (6.0.3.1)
actionview (= 6.0.3.1)
activesupport (= 6.0.3.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
@ -222,30 +222,30 @@ GEM
actionpack-xml_parser (2.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
actiontext (6.0.2.2)
actionpack (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
actiontext (6.0.3.1)
actionpack (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
nokogiri (>= 1.8.5)
actionview (6.0.2.2)
activesupport (= 6.0.2.2)
actionview (6.0.3.1)
activesupport (= 6.0.3.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.2.2)
activesupport (= 6.0.2.2)
activejob (6.0.3.1)
activesupport (= 6.0.3.1)
globalid (>= 0.3.6)
activemodel (6.0.2.2)
activesupport (= 6.0.2.2)
activemodel (6.0.3.1)
activesupport (= 6.0.3.1)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (~> 3.1)
activerecord (6.0.2.2)
activemodel (= 6.0.2.2)
activesupport (= 6.0.2.2)
activerecord (6.0.3.1)
activemodel (= 6.0.3.1)
activesupport (= 6.0.3.1)
activerecord-import (1.0.4)
activerecord (>= 3.2)
activerecord-nulldb-adapter (0.4.0)
@ -256,17 +256,17 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3)
railties (>= 4.0)
activestorage (6.0.2.2)
actionpack (= 6.0.2.2)
activejob (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (6.0.3.1)
actionpack (= 6.0.3.1)
activejob (= 6.0.3.1)
activerecord (= 6.0.3.1)
marcel (~> 0.3.1)
activesupport (6.0.2.2)
activesupport (6.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2)
zeitwerk (~> 2.2, >= 2.2.2)
acts_as_list (0.9.19)
activerecord (>= 3.0)
acts_as_tree (2.9.1)
@ -597,7 +597,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.4.0)
loofah (2.5.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -611,11 +611,11 @@ GEM
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mimemagic (0.3.4)
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minisyntax (0.2.5)
minitest (5.14.0)
minitest (5.14.1)
mixlib-shellout (2.1.0)
msgpack (1.3.3)
multi_json (1.14.1)
@ -732,20 +732,20 @@ GEM
rack_session_access (0.2.0)
builder (>= 2.0.0)
rack (>= 1.0.0)
rails (6.0.2.2)
actioncable (= 6.0.2.2)
actionmailbox (= 6.0.2.2)
actionmailer (= 6.0.2.2)
actionpack (= 6.0.2.2)
actiontext (= 6.0.2.2)
actionview (= 6.0.2.2)
activejob (= 6.0.2.2)
activemodel (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
actionmailer (= 6.0.3.1)
actionpack (= 6.0.3.1)
actiontext (= 6.0.3.1)
actionview (= 6.0.3.1)
activejob (= 6.0.3.1)
activemodel (= 6.0.3.1)
activerecord (= 6.0.3.1)
activestorage (= 6.0.3.1)
activesupport (= 6.0.3.1)
bundler (>= 1.3.0)
railties (= 6.0.2.2)
railties (= 6.0.3.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
@ -764,9 +764,9 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (6.0.2.2)
actionpack (= 6.0.2.2)
activesupport (= 6.0.2.2)
railties (6.0.3.1)
actionpack (= 6.0.3.1)
activesupport (= 6.0.3.1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@ -922,7 +922,7 @@ GEM
ttfunk (1.6.2.1)
typed_dag (2.0.2)
rails (>= 5.0.4)
tzinfo (1.2.6)
tzinfo (1.2.7)
thread_safe (~> 0.1)
tzinfo-data (1.2019.3)
tzinfo (>= 1.0.0)
@ -1085,7 +1085,7 @@ DEPENDENCIES
rack-protection (~> 2.0.8)
rack-test (~> 1.1.0)
rack_session_access
rails (~> 6.0.2)
rails (~> 6.0.3.1)
rails-controller-testing (~> 1.0.2)
rails-i18n (~> 6.0.0)
rails_12factor

@ -37,9 +37,9 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
end
def available_operators
[::Queries::Operators::All,
::Queries::Operators::None,
::Queries::Operators::Equals]
all_and_none = [::Queries::Operators::All, ::Queries::Operators::None]
all_and_none + (super - all_and_none)
end
def available?
@ -49,7 +49,7 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
end
def type
:list
:list_optional
end
def human_name
@ -71,21 +71,25 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
end
def where
ids = [project.id]
case operator
when '='
# include the selected subprojects
ids += values.each(&:to_i)
when '*'
ids += visible_subproject_array.map(&:first)
end
"#{Project.table_name}.id IN (%s)" % ids.join(',')
"#{Project.table_name}.id IN (%s)" % ids_for_where.join(',')
end
private
def ids_for_where
[project.id] + case operator
when ::Queries::Operators::Equals.symbol
# include the selected subprojects
value_ints
when ::Queries::Operators::All.symbol
visible_subproject_ids
when ::Queries::Operators::NotEquals.symbol
visible_subproject_ids - value_ints
else # None
[]
end
end
def visible_subproject_array
visible_subprojects.pluck(:id, :name)
end
@ -101,14 +105,11 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
end
end
def operator_strategy
case operator
when '*'
::Queries::Operators::All
when '!*'
::Queries::Operators::None
when '='
::Queries::Operators::Equals
end
def visible_subproject_ids
visible_subproject_array.map(&:first)
end
def value_ints
values.map(&:to_i)
end
end

@ -33,6 +33,11 @@ module Projects
def after_perform(attributes_call)
attributes_call.result.add_member!(user, Role.in_new_project) unless user.admin?
OpenProject::Notifications.send(
OpenProject::Events::PROJECT_CREATED,
project: attributes_call.result
)
super
end
end

@ -61,11 +61,11 @@ module Projects
def notify_on_identifier_renamed
return unless memoized_changes['identifier']
OpenProject::Notifications.send('project_renamed', project: model)
OpenProject::Notifications.send(OpenProject::Events::PROJECT_RENAMED, project: model)
end
def send_update_notification
OpenProject::Notifications.send('project_updated', project: model)
OpenProject::Notifications.send(OpenProject::Events::PROJECT_UPDATED, project: model)
end
def only_custom_values_updated?

@ -307,10 +307,11 @@ export class ResourceChangeset<T extends HalResource|{ [key:string]:unknown; } =
if (this.pristineResource.isNew) {
// If the resource is new, we need to pass the entire form payload
// to let all default values be transmitted (type, status, etc.)
// We clone the object to avoid later manipulations to affect the original resource.
if (this.form$.value) {
payload = this.form$.value.payload.$source;
payload = _.cloneDeep(this.form$.value.payload.$source);
} else {
payload = this.pristineResource.$source;
payload = _.cloneDeep(this.pristineResource.$source);
}
// Add attachments to be assigned.

@ -39,5 +39,9 @@ module OpenProject
module Events
AGGREGATED_WORK_PACKAGE_JOURNAL_READY = "aggregated_work_package_journal_ready".freeze
NEW_TIME_ENTRY_CREATED = "new_time_entry_created".freeze
PROJECT_CREATED = "project_created".freeze
PROJECT_UPDATED = "project_updated".freeze
PROJECT_RENAMED = "project_renamed".freeze
end
end

@ -51,7 +51,9 @@ module OpenProject::Plugins
end
def self.providers
filtered_strategies strategies.values.flatten.map(&:call).flatten.map(&:to_hash)
RequestStore.fetch(:openproject_omniauth_filtered_strategies) do
filtered_strategies strategies.values.flatten.map(&:call).flatten.map(&:to_hash)
end
end
def self.filtered_strategies(options)

@ -1,6 +1,41 @@
require 'omniauth-saml'
module OpenProject
module AuthSaml
def self.configuration
RequestStore.fetch(:openproject_omniauth_saml_provider) do
@saml_settings ||= load_global_settings!
@saml_settings.deep_merge(settings_from_db)
end
end
##
# Loads the settings once to avoid accessing the file in each request
def self.load_global_settings!
Hash(settings_from_config || settings_from_yaml).with_indifferent_access
end
def self.settings_from_db
value = Hash(Setting.plugin_openproject_auth_saml).with_indifferent_access[:providers]
value.is_a?(Hash) ? value : {}
end
def self.settings_from_config
if OpenProject::Configuration['saml'].present?
Rails.logger.info("[auth_saml] Registering saml integration from configuration.yml")
OpenProject::Configuration['saml']
end
end
def self.settings_from_yaml
if (settings = Rails.root.join('config', 'plugins', 'auth_saml', 'settings.yml')).exist?
Rails.logger.info("[auth_saml] Registering saml integration from settings file")
YAML::load(File.open(settings)).symbolize_keys
end
end
class Engine < ::Rails::Engine
engine_name :openproject_auth_saml
@ -9,7 +44,8 @@ module OpenProject
register 'openproject-auth_saml',
author_url: 'https://github.com/finnlabs/openproject-auth_saml',
bundled: true
bundled: true,
settings: { default: { "providers" => nil }}
assets %w(
auth_saml/**
@ -31,27 +67,15 @@ module OpenProject
end
register_auth_providers do
configuration = if OpenProject::Configuration['saml'].present?
Rails.logger.info("[auth_saml] Registering saml integration from configuration.yml")
OpenProject::Configuration['saml']
elsif (settings = Rails.root.join('config', 'plugins', 'auth_saml', 'settings.yml')).exist?
Rails.logger.info("[auth_saml] Registering saml integration from settings file")
YAML::load(File.open(settings)).symbolize_keys
end
if configuration
strategy :saml do
configuration.values.map do |h|
h[:openproject_attribute_map] = Proc.new do |auth|
{
login: auth[:uid],
admin: (auth.info['admin'].to_s.downcase == "true")
}
end
h.symbolize_keys
strategy :saml do
OpenProject::AuthSaml.configuration.values.map do |h|
h[:openproject_attribute_map] = Proc.new do |auth|
{
login: auth[:uid],
admin: (auth.info['admin'].to_s.downcase == "true")
}
end
h.symbolize_keys
end
end
end

@ -0,0 +1,52 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'open_project/auth_saml'
describe OpenProject::AuthSaml do
describe ".configuration" do
let(:config) { OpenProject::AuthSaml.configuration }
context(
"with configuration",
with_config: {
saml: {
my_saml: {
name: "saml",
display_name: "My SSO"
}
}
}
) do
it "contains the configuration from OpenProject::Configuration (or settings.yml) by default" do
expect(config[:my_saml][:name]).to eq 'saml'
expect(config[:my_saml][:display_name]).to eq 'My SSO'
end
context(
"with settings override from database",
with_settings: {
plugin_openproject_auth_saml: {
providers: {
my_saml: {
display_name: "Your SSO"
},
new_saml: {
name: "new_saml",
display_name: "Another SAML"
}
}
}
}
) do
it "overrides the existing configuration where defined" do
expect(config[:my_saml][:name]).to eq 'saml'
expect(config[:my_saml][:display_name]).to eq 'Your SSO'
end
it "defines new providers if given" do
expect(config[:new_saml][:name]).to eq 'new_saml'
expect(config[:new_saml][:display_name]).to eq 'Another SAML'
end
end
end
end
end

@ -0,0 +1,2 @@
# -- load spec_helper from OpenProject core
require "spec_helper"

@ -86,7 +86,7 @@ describe 'My page time entries current user widget spec', type: :feature, js: tr
hours: 4
end
let!(:custom_field) do
FactoryBot.create :time_entry_custom_field, field_format: 'string'
FactoryBot.create :time_entry_custom_field, field_format: 'text'
end
let(:other_user) do
FactoryBot.create(:user)
@ -99,7 +99,7 @@ describe 'My page time entries current user widget spec', type: :feature, js: tr
let(:my_page) do
Pages::My::Page.new
end
let(:cf_field) { ::EditField.new(page, "customField#{custom_field.id}") }
let(:cf_field) { ::TextEditorField.new(page, "customField#{custom_field.id}") }
let(:time_logging_modal) { ::Components::TimeLoggingModal.new }
before do

@ -0,0 +1,51 @@
require 'rest-client'
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 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-2017 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 ProjectWebhookJob < RepresentedWebhookJob
def payload_key
:project
end
def accepted_in_project?
if event_name == 'project:created'
true
else
webhook.enabled_for_project?(resource.id)
end
end
def payload_representer
User.system.run_given do |user|
::API::V3::Projects::ProjectRepresenter
.create(resource, current_user: user, embed_links: true)
end
end
end

@ -35,7 +35,7 @@ module OpenProject::Webhooks
end
def resources
%i(work_package time_entry)
%i(project work_package time_entry)
end
end
end

@ -0,0 +1,33 @@
require_relative 'base'
module OpenProject::Webhooks::EventResources
class Project < Base
class << self
def notification_names
[
OpenProject::Events::PROJECT_CREATED,
OpenProject::Events::PROJECT_UPDATED
]
end
def available_actions
%i(updated created)
end
def resource_name
I18n.t :label_project_plural
end
protected
def handle_notification(payload, event_name)
action = event_name.split('_').last
event_name = prefixed_event_name(action)
active_webhooks.with_event_name(event_name).pluck(:id).each do |id|
ProjectWebhookJob.perform_later(id, payload[:project], event_name)
end
end
end
end
end

@ -0,0 +1,152 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 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-2017 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 ProjectWebhookJob, type: :job, webmock: true do
shared_let(:user) { FactoryBot.create :admin }
shared_let(:request_url) { "http://example.net/test/42" }
shared_let(:project) { FactoryBot.create :project, name: 'Foo Bar', hours: 10 }
shared_let(:webhook) { FactoryBot.create :webhook, all_projects: true, url: request_url, secret: nil }
shared_examples "a project webhook call" do
let(:event) { "project:created" }
let(:job) { TimeEntryWebhookJob.perform_now(webhook.id, project, event) }
let(:stubbed_url) { request_url }
let(:request_headers) do
{ content_type: "application/json", accept: "application/json" }
end
let(:response_code) { 200 }
let(:response_body) { "hook called" }
let(:response_headers) do
{ content_type: "text/plain", x_spec: "foobar" }
end
let(:stub) do
stub_request(:post, stubbed_url.sub("http://", ""))
.with(
body: hash_including(
"action" => event,
"poroject" => hash_including(
"_type" => "Project",
"name" => 'Foo Bar'
)
),
headers: request_headers
)
.to_return(
status: response_code,
body: response_body,
headers: response_headers
)
end
subject do
begin
job
rescue
# ignoring it as it's expected to throw exceptions in certain scenarios
nil
end
end
before do
allow(::Webhooks::Webhook).to receive(:find).with(webhook.id).and_return(webhook)
login_as user
stub
end
it 'requests with all projects' do
allow(webhook)
.to receive(:enabled_for_project?).with(project.id)
.and_call_original
subject
expect(stub).to have_been_requested
end
it 'does not request when project does not match unless create case' do
allow(webhook)
.to receive(:enabled_for_project?).with(project.id)
.and_return(false)
subject
if event_name == 'project:created'
expect(stub).to have_been_requested
else
expect(stub).not_to have_been_requested
end
end
describe 'successful flow' do
before do
subject
end
it "calls the webhook url" do
expect(stub).to have_been_requested
end
it "creates a log for the call" do
log = Webhooks::Log.last
expect(log.webhook).to eq webhook
expect(log.url).to eq webhook.url
expect(log.event_name).to eq event
expect(log.request_headers).to eq request_headers
expect(log.response_code).to eq response_code
expect(log.response_body).to eq response_body
expect(log.response_headers).to eq response_headers
end
end
end
describe "triggering a project update" do
it_behaves_like "a project webhook call" do
let(:event) { "project:updated" }
end
end
describe "triggering a projec creation" do
it_behaves_like "a project webhook call" do
let(:event) { "project:created" }
end
end
describe "triggering a work package create with an invalid url" do
it_behaves_like "a project webhook call" do
let(:event) { "project:update" }
let(:response_code) { 404 }
let(:response_body) { "not found" }
end
end
end

@ -30,7 +30,7 @@ require 'spec_helper'
describe Queries::WorkPackages::Filter::SubprojectFilter, type: :model do
it_behaves_like 'basic query filter' do
let(:type) { :list }
let(:type) { :list_optional }
let(:class_key) { :subproject_id }
let(:name) { I18n.t('query_fields.subproject_id') }
let(:project) { FactoryBot.build_stubbed :project }
@ -125,5 +125,59 @@ describe Queries::WorkPackages::Filter::SubprojectFilter, type: :model do
.to be_ar_object_filter
end
end
describe '#available_operators' do
it 'is the same as for every list_optional but the `all` operator comes first' do
expect(instance.available_operators)
.to eql([::Queries::Operators::All, ::Queries::Operators::None,
::Queries::Operators::Equals, ::Queries::Operators::NotEquals])
end
end
describe '#where' do
let(:subproject1) { FactoryBot.build_stubbed(:project) }
let(:subproject2) { FactoryBot.build_stubbed(:project) }
let(:projects) { [subproject1, subproject2] }
context 'for the equals operator' do
let(:operator) { '=' }
let(:values) { [subproject1.id.to_s] }
it 'returns an sql filtering for project id eql self or specified values' do
expect(instance.where)
.to eql("projects.id IN (#{project.id},#{subproject1.id})")
end
end
context 'for the not equals operator' do
let(:operator) { '!' }
let(:values) { [subproject1.id.to_s] }
it 'returns an sql filtering for project id eql self and all subprojects excluding specified values' do
expect(instance.where)
.to eql("projects.id IN (#{project.id},#{subproject2.id})")
end
end
context 'for the all operator' do
let(:operator) { '*' }
let(:values) { [] }
it 'returns an sql filtering for project id eql self and all subprojects' do
expect(instance.where)
.to eql("projects.id IN (#{project.id},#{subproject1.id},#{subproject2.id})")
end
end
context 'for the none operator' do
let(:operator) { '!*' }
let(:values) { [] }
it 'returns an sql filtering for only the project id (and by that excluding all subprojects)' do
expect(instance.where)
.to eql("projects.id IN (#{project.id})")
end
end
end
end
end

Loading…
Cancel
Save