diff --git a/Gemfile b/Gemfile index da122ee614..03660c36cd 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 236d9771c0..68a81ffd74 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/models/queries/work_packages/filter/subproject_filter.rb b/app/models/queries/work_packages/filter/subproject_filter.rb index bf67731c31..8ecc6aae61 100644 --- a/app/models/queries/work_packages/filter/subproject_filter.rb +++ b/app/models/queries/work_packages/filter/subproject_filter.rb @@ -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 diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 11f0e778b4..5ca1827eb7 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -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 diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 4ef200eaae..c4f58fd6be 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -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? diff --git a/frontend/src/app/modules/fields/changeset/resource-changeset.ts b/frontend/src/app/modules/fields/changeset/resource-changeset.ts index 8da53b744a..db0fb489c3 100644 --- a/frontend/src/app/modules/fields/changeset/resource-changeset.ts +++ b/frontend/src/app/modules/fields/changeset/resource-changeset.ts @@ -307,10 +307,11 @@ export class ResourceChangeset 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 diff --git a/modules/auth_saml/spec/lib/open_project/auth_saml_spec.rb b/modules/auth_saml/spec/lib/open_project/auth_saml_spec.rb new file mode 100644 index 0000000000..eb0e8e7480 --- /dev/null +++ b/modules/auth_saml/spec/lib/open_project/auth_saml_spec.rb @@ -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 diff --git a/modules/auth_saml/spec/spec_helper.rb b/modules/auth_saml/spec/spec_helper.rb new file mode 100644 index 0000000000..4351818bd6 --- /dev/null +++ b/modules/auth_saml/spec/spec_helper.rb @@ -0,0 +1,2 @@ +# -- load spec_helper from OpenProject core +require "spec_helper" diff --git a/modules/my_page/spec/features/my/time_entries_current_user_spec.rb b/modules/my_page/spec/features/my/time_entries_current_user_spec.rb index 4749e2c0e4..15a7d3c315 100644 --- a/modules/my_page/spec/features/my/time_entries_current_user_spec.rb +++ b/modules/my_page/spec/features/my/time_entries_current_user_spec.rb @@ -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 diff --git a/modules/webhooks/app/workers/project_webhook_job.rb b/modules/webhooks/app/workers/project_webhook_job.rb new file mode 100644 index 0000000000..2fbe84a653 --- /dev/null +++ b/modules/webhooks/app/workers/project_webhook_job.rb @@ -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 diff --git a/modules/webhooks/lib/open_project/webhooks/event_resources.rb b/modules/webhooks/lib/open_project/webhooks/event_resources.rb index 75beaa82a2..a19e31e6c8 100644 --- a/modules/webhooks/lib/open_project/webhooks/event_resources.rb +++ b/modules/webhooks/lib/open_project/webhooks/event_resources.rb @@ -35,7 +35,7 @@ module OpenProject::Webhooks end def resources - %i(work_package time_entry) + %i(project work_package time_entry) end end end diff --git a/modules/webhooks/lib/open_project/webhooks/event_resources/project.rb b/modules/webhooks/lib/open_project/webhooks/event_resources/project.rb new file mode 100644 index 0000000000..5b357acf2b --- /dev/null +++ b/modules/webhooks/lib/open_project/webhooks/event_resources/project.rb @@ -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 diff --git a/modules/webhooks/spec/workers/project_webhook_job.rb b/modules/webhooks/spec/workers/project_webhook_job.rb new file mode 100644 index 0000000000..f032b82493 --- /dev/null +++ b/modules/webhooks/spec/workers/project_webhook_job.rb @@ -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 diff --git a/spec/models/queries/work_packages/filter/subproject_filter_spec.rb b/spec/models/queries/work_packages/filter/subproject_filter_spec.rb index c445451e6e..fed31690ef 100644 --- a/spec/models/queries/work_packages/filter/subproject_filter_spec.rb +++ b/spec/models/queries/work_packages/filter/subproject_filter_spec.rb @@ -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