scimitar 1.8.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
- data/app/controllers/scimitar/application_controller.rb +13 -41
- data/app/controllers/scimitar/schemas_controller.rb +0 -5
- data/app/models/scimitar/complex_types/address.rb +6 -0
- data/app/models/scimitar/engine_configuration.rb +5 -13
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/lists/query_parser.rb +10 -25
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/base.rb +4 -17
- data/app/models/scimitar/resources/mixin.rb +42 -539
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +5 -14
- data/app/models/scimitar/schema/base.rb +1 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/app/models/scimitar/service_provider_configuration.rb +3 -14
- data/config/initializers/scimitar.rb +3 -28
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +2 -7
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +8 -36
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
- data/spec/apps/dummy/config/routes.rb +7 -28
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -11
- data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
- data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
- data/spec/models/scimitar/resources/base_spec.rb +70 -216
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +129 -1447
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +0 -10
- data/spec/requests/active_record_backed_resources_controller_spec.rb +68 -787
- data/spec/requests/application_controller_spec.rb +3 -16
- data/spec/spec_helper.rb +0 -8
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
- metadata +14 -25
- data/LICENSE.txt +0 -21
- data/README.md +0 -710
- data/lib/scimitar/support/utilities.rb +0 -51
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
| @@ -1,13 +1,8 @@ | |
| 1 1 | 
             
            class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
         | 
| 2 2 | 
             
              def change
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
                  t. | 
| 5 | 
            -
                  t. | 
| 6 | 
            -
             | 
| 7 | 
            -
                  # The 'foreign_key:' option (used above) only works for 'id' column names
         | 
| 8 | 
            -
                  # but the test data has a column named 'primary_key' for 'mock_users'.
         | 
| 9 | 
            -
                  #
         | 
| 10 | 
            -
                  t.foreign_key :mock_users, primary_key: :primary_key
         | 
| 3 | 
            +
                create_join_table :mock_groups, :mock_users do |t|
         | 
| 4 | 
            +
                  t.index [:mock_group_id, :mock_user_id]
         | 
| 5 | 
            +
                  t.index [:mock_user_id, :mock_group_id]
         | 
| 11 6 | 
             
                end
         | 
| 12 7 | 
             
              end
         | 
| 13 8 | 
             
            end
         | 
| @@ -24,26 +24,19 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do | |
| 24 24 |  | 
| 25 25 | 
             
              create_table "mock_groups_users", id: false, force: :cascade do |t|
         | 
| 26 26 | 
             
                t.bigint "mock_group_id", null: false
         | 
| 27 | 
            -
                t. | 
| 28 | 
            -
                t.index ["mock_group_id"], name: " | 
| 29 | 
            -
                t.index ["mock_user_id"], name: " | 
| 27 | 
            +
                t.bigint "mock_user_id", null: false
         | 
| 28 | 
            +
                t.index ["mock_group_id", "mock_user_id"], name: "index_mock_groups_users_on_mock_group_id_and_mock_user_id"
         | 
| 29 | 
            +
                t.index ["mock_user_id", "mock_group_id"], name: "index_mock_groups_users_on_mock_user_id_and_mock_group_id"
         | 
| 30 30 | 
             
              end
         | 
| 31 31 |  | 
| 32 | 
            -
              create_table "mock_users",  | 
| 33 | 
            -
                t.datetime "created_at", null: false
         | 
| 34 | 
            -
                t.datetime "updated_at", null: false
         | 
| 32 | 
            +
              create_table "mock_users", force: :cascade do |t|
         | 
| 35 33 | 
             
                t.text "scim_uid"
         | 
| 36 34 | 
             
                t.text "username"
         | 
| 37 | 
            -
                t.text "password"
         | 
| 38 35 | 
             
                t.text "first_name"
         | 
| 39 36 | 
             
                t.text "last_name"
         | 
| 40 37 | 
             
                t.text "work_email_address"
         | 
| 41 38 | 
             
                t.text "home_email_address"
         | 
| 42 39 | 
             
                t.text "work_phone_number"
         | 
| 43 | 
            -
                t.text "organization"
         | 
| 44 | 
            -
                t.text "department"
         | 
| 45 40 | 
             
              end
         | 
| 46 41 |  | 
| 47 | 
            -
              add_foreign_key "mock_groups_users", "mock_groups"
         | 
| 48 | 
            -
              add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
         | 
| 49 42 | 
             
            end
         | 
| @@ -24,7 +24,7 @@ RSpec.describe Scimitar::ApplicationController do | |
| 24 24 | 
             
                  get :index, params: { format: :scim }
         | 
| 25 25 | 
             
                  expect(response).to be_ok
         | 
| 26 26 | 
             
                  expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
         | 
| 27 | 
            -
                  expect(response.headers[' | 
| 27 | 
            +
                  expect(response.headers['WWW_AUTHENTICATE']).to eql('Basic')
         | 
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| 30 30 | 
             
                it 'renders failure with bad password' do
         | 
| @@ -84,61 +84,7 @@ RSpec.describe Scimitar::ApplicationController do | |
| 84 84 | 
             
                  get :index, params: { format: :scim }
         | 
| 85 85 | 
             
                  expect(response).to be_ok
         | 
| 86 86 | 
             
                  expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
         | 
| 87 | 
            -
                  expect(response.headers[' | 
| 88 | 
            -
                end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                it 'renders failure with bad token' do
         | 
| 91 | 
            -
                  request.env['HTTP_AUTHORIZATION'] = 'Bearer Invalid'
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                  get :index, params: { format: :scim }
         | 
| 94 | 
            -
                  expect(response).not_to be_ok
         | 
| 95 | 
            -
                end
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                it 'renders failure with blank token' do
         | 
| 98 | 
            -
                  request.env['HTTP_AUTHORIZATION'] = 'Bearer'
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                  get :index, params: { format: :scim }
         | 
| 101 | 
            -
                  expect(response).not_to be_ok
         | 
| 102 | 
            -
                end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                it 'renders failure with missing header' do
         | 
| 105 | 
            -
                  get :index, params: { format: :scim }
         | 
| 106 | 
            -
                  expect(response).not_to be_ok
         | 
| 107 | 
            -
                end
         | 
| 108 | 
            -
              end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
              context 'authenticator evaluated within controller context' do
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                # Define a controller with a custom instance method 'valid_token'.
         | 
| 113 | 
            -
                #
         | 
| 114 | 
            -
                controller do
         | 
| 115 | 
            -
                  def index
         | 
| 116 | 
            -
                    render json: { 'message' => 'cool, cool!' }, format: :scim
         | 
| 117 | 
            -
                  end
         | 
| 118 | 
            -
             | 
| 119 | 
            -
                  def valid_token
         | 
| 120 | 
            -
                    'B'
         | 
| 121 | 
            -
                  end
         | 
| 122 | 
            -
                end
         | 
| 123 | 
            -
             | 
| 124 | 
            -
                # Call the above controller method from the token authenticator Proc,
         | 
| 125 | 
            -
                # proving that it was executed in the controller's context.
         | 
| 126 | 
            -
                #
         | 
| 127 | 
            -
                before do
         | 
| 128 | 
            -
                  Scimitar.engine_configuration = Scimitar::EngineConfiguration.new(
         | 
| 129 | 
            -
                    token_authenticator: Proc.new do | token, options |
         | 
| 130 | 
            -
                      token == self.valid_token()
         | 
| 131 | 
            -
                    end
         | 
| 132 | 
            -
                  )
         | 
| 133 | 
            -
                end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                it 'renders success when valid creds are given' do
         | 
| 136 | 
            -
                  request.env['HTTP_AUTHORIZATION'] = 'Bearer B'
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                  get :index, params: { format: :scim }
         | 
| 139 | 
            -
                  expect(response).to be_ok
         | 
| 140 | 
            -
                  expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
         | 
| 141 | 
            -
                  expect(response.headers['WWW-Authenticate']).to eql('Bearer')
         | 
| 87 | 
            +
                  expect(response.headers['WWW_AUTHENTICATE']).to eql('Bearer')
         | 
| 142 88 | 
             
                end
         | 
| 143 89 |  | 
| 144 90 | 
             
                it 'renders failure with bad token' do
         | 
| @@ -223,74 +169,5 @@ RSpec.describe Scimitar::ApplicationController do | |
| 223 169 | 
             
                  expect(parsed_body).to include('status' => '500')
         | 
| 224 170 | 
             
                  expect(parsed_body).to include('detail' => 'Bang')
         | 
| 225 171 | 
             
                end
         | 
| 226 | 
            -
             | 
| 227 | 
            -
                context 'with an exception reporter' do
         | 
| 228 | 
            -
                  around :each do | example |
         | 
| 229 | 
            -
                    original_configuration = Scimitar.engine_configuration.exception_reporter
         | 
| 230 | 
            -
                    Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
         | 
| 231 | 
            -
                      @exception = exception
         | 
| 232 | 
            -
                    end
         | 
| 233 | 
            -
                    example.run()
         | 
| 234 | 
            -
                  ensure
         | 
| 235 | 
            -
                    Scimitar.engine_configuration.exception_reporter = original_configuration
         | 
| 236 | 
            -
                  end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                  context 'and "internal server error"' do
         | 
| 239 | 
            -
                    it 'is invoked' do
         | 
| 240 | 
            -
                      get :index, params: { format: :scim }
         | 
| 241 | 
            -
             | 
| 242 | 
            -
                      expect(@exception).to be_a(RuntimeError)
         | 
| 243 | 
            -
                      expect(@exception.message).to eql('Bang')
         | 
| 244 | 
            -
                    end
         | 
| 245 | 
            -
                  end
         | 
| 246 | 
            -
             | 
| 247 | 
            -
                  context 'and "not found"' do
         | 
| 248 | 
            -
                    controller do
         | 
| 249 | 
            -
                      def index
         | 
| 250 | 
            -
                        handle_resource_not_found(ActiveRecord::RecordNotFound.new(42))
         | 
| 251 | 
            -
                      end
         | 
| 252 | 
            -
                    end
         | 
| 253 | 
            -
             | 
| 254 | 
            -
                    it 'is invoked' do
         | 
| 255 | 
            -
                      get :index, params: { format: :scim }
         | 
| 256 | 
            -
             | 
| 257 | 
            -
                      expect(@exception).to be_a(ActiveRecord::RecordNotFound)
         | 
| 258 | 
            -
                      expect(@exception.message).to eql('42')
         | 
| 259 | 
            -
                    end
         | 
| 260 | 
            -
                  end
         | 
| 261 | 
            -
             | 
| 262 | 
            -
                  context 'and bad JSON' do
         | 
| 263 | 
            -
                    controller do
         | 
| 264 | 
            -
                      def index
         | 
| 265 | 
            -
                        begin
         | 
| 266 | 
            -
                          raise 'Hello'
         | 
| 267 | 
            -
                        rescue
         | 
| 268 | 
            -
                          raise ActionDispatch::Http::Parameters::ParseError
         | 
| 269 | 
            -
                        end
         | 
| 270 | 
            -
                      end
         | 
| 271 | 
            -
                    end
         | 
| 272 | 
            -
             | 
| 273 | 
            -
                    it 'is invoked' do
         | 
| 274 | 
            -
                      get :index, params: { format: :scim }
         | 
| 275 | 
            -
             | 
| 276 | 
            -
                      expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError)
         | 
| 277 | 
            -
                      expect(@exception.message).to eql('Hello')
         | 
| 278 | 
            -
                    end
         | 
| 279 | 
            -
                  end
         | 
| 280 | 
            -
             | 
| 281 | 
            -
                  context 'and a bad content type' do
         | 
| 282 | 
            -
                    controller do
         | 
| 283 | 
            -
                      def index; end
         | 
| 284 | 
            -
                    end
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                    it 'is invoked' do
         | 
| 287 | 
            -
                      request.headers['Content-Type'] = 'text/plain'
         | 
| 288 | 
            -
                      get :index
         | 
| 289 | 
            -
             | 
| 290 | 
            -
                      expect(@exception).to be_a(Scimitar::ErrorResponse)
         | 
| 291 | 
            -
                      expect(@exception.message).to eql('Only application/scim+json type is accepted.')
         | 
| 292 | 
            -
                    end
         | 
| 293 | 
            -
                  end
         | 
| 294 | 
            -
                end # "context 'exception reporter' do"
         | 
| 295 | 
            -
              end # "context 'error handling' do"
         | 
| 172 | 
            +
              end
         | 
| 296 173 | 
             
            end
         | 
| @@ -9,8 +9,8 @@ RSpec.describe Scimitar::ResourceTypesController do | |
| 9 9 | 
             
                it 'renders the resource type for user' do
         | 
| 10 10 | 
             
                  get :index, format: :scim
         | 
| 11 11 | 
             
                  response_hash = JSON.parse(response.body)
         | 
| 12 | 
            -
                  expected_response = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User' | 
| 13 | 
            -
                                        Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group' | 
| 12 | 
            +
                  expected_response = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User')),
         | 
| 13 | 
            +
                                        Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group'))
         | 
| 14 14 | 
             
                  ].to_json
         | 
| 15 15 |  | 
| 16 16 | 
             
                  response_hash = JSON.parse(response.body)
         | 
| @@ -1,7 +1,6 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            RSpec.describe Scimitar::SchemasController do
         | 
| 4 | 
            -
              routes { Scimitar::Engine.routes }
         | 
| 5 4 |  | 
| 6 5 | 
             
              before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
         | 
| 7 6 |  | 
| @@ -15,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do | |
| 15 14 | 
             
                  get :index, params: { format: :scim }
         | 
| 16 15 | 
             
                  expect(response).to be_ok
         | 
| 17 16 | 
             
                  parsed_body = JSON.parse(response.body)
         | 
| 18 | 
            -
                  expect(parsed_body.length).to eql( | 
| 17 | 
            +
                  expect(parsed_body.length).to eql(2)
         | 
| 19 18 | 
             
                  schema_names = parsed_body.map {|schema| schema['name']}
         | 
| 20 | 
            -
                  expect(schema_names).to match_array(['User', ' | 
| 19 | 
            +
                  expect(schema_names).to match_array(['User', 'Group'])
         | 
| 21 20 | 
             
                end
         | 
| 22 21 |  | 
| 23 22 | 
             
                it 'returns only the User schema when its id is provided' do
         | 
| @@ -27,13 +26,6 @@ RSpec.describe Scimitar::SchemasController do | |
| 27 26 | 
             
                  expect(parsed_body['name']).to eql('User')
         | 
| 28 27 | 
             
                end
         | 
| 29 28 |  | 
| 30 | 
            -
                it 'includes the controller customised schema location' do
         | 
| 31 | 
            -
                  get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
         | 
| 32 | 
            -
                  expect(response).to be_ok
         | 
| 33 | 
            -
                  parsed_body = JSON.parse(response.body)
         | 
| 34 | 
            -
                  expect(parsed_body.dig('meta', 'location')).to eq scim_schemas_url(name: Scimitar::Schema::User.id, test: 1)
         | 
| 35 | 
            -
                end
         | 
| 36 | 
            -
             | 
| 37 29 | 
             
                it 'returns only the Group schema when its id is provided' do
         | 
| 38 30 | 
             
                  get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
         | 
| 39 31 | 
             
                  expect(response).to be_ok
         | 
| @@ -2,8 +2,8 @@ require 'spec_helper' | |
| 2 2 |  | 
| 3 3 | 
             
            RSpec.describe Scimitar::ComplexTypes::Address do
         | 
| 4 4 | 
             
              context '#as_json' do
         | 
| 5 | 
            -
                it 'assumes  | 
| 6 | 
            -
                  expect(described_class.new.as_json).to eq( | 
| 5 | 
            +
                it 'assumes a type of "work" as a default' do
         | 
| 6 | 
            +
                  expect(described_class.new.as_json).to eq('type' => 'work')
         | 
| 7 7 | 
             
                end
         | 
| 8 8 |  | 
| 9 9 | 
             
                it 'allows a custom address type' do
         | 
| @@ -11,8 +11,9 @@ RSpec.describe Scimitar::ComplexTypes::Address do | |
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 13 | 
             
                it 'shows the set address' do
         | 
| 14 | 
            -
                  expect(described_class.new(country: 'NZ').as_json).to eq('country' => 'NZ')
         | 
| 14 | 
            +
                  expect(described_class.new(country: 'NZ').as_json).to eq('type' => 'work', 'country' => 'NZ')
         | 
| 15 15 | 
             
                end
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 18 | 
             
            end
         | 
| 19 | 
            +
             | 
| @@ -405,19 +405,19 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 405 405 | 
             
                  query = @instance.to_activerecord_query(MockUser.all)
         | 
| 406 406 |  | 
| 407 407 | 
             
                  expect(query.count).to eql(1)
         | 
| 408 | 
            -
                  expect(query.pluck(: | 
| 408 | 
            +
                  expect(query.pluck(:id)).to eql([user_1.id])
         | 
| 409 409 |  | 
| 410 410 | 
             
                  @instance.parse('name.givenName sw J') # First name starts with 'J'
         | 
| 411 411 | 
             
                  query = @instance.to_activerecord_query(MockUser.all)
         | 
| 412 412 |  | 
| 413 413 | 
             
                  expect(query.count).to eql(2)
         | 
| 414 | 
            -
                  expect(query.pluck(: | 
| 414 | 
            +
                  expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
         | 
| 415 415 |  | 
| 416 416 | 
             
                  @instance.parse('name.familyName ew he') # Last name ends with 'he'
         | 
| 417 417 | 
             
                  query = @instance.to_activerecord_query(MockUser.all)
         | 
| 418 418 |  | 
| 419 419 | 
             
                  expect(query.count).to eql(1)
         | 
| 420 | 
            -
                  expect(query.pluck(: | 
| 420 | 
            +
                  expect(query.pluck(:id)).to eql([user_2.id])
         | 
| 421 421 |  | 
| 422 422 | 
             
                  # Test presence
         | 
| 423 423 |  | 
| @@ -425,7 +425,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 425 425 | 
             
                  query = @instance.to_activerecord_query(MockUser.all)
         | 
| 426 426 |  | 
| 427 427 | 
             
                  expect(query.count).to eql(2)
         | 
| 428 | 
            -
                  expect(query.pluck(: | 
| 428 | 
            +
                  expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
         | 
| 429 429 |  | 
| 430 430 | 
             
                  # Test a simple not-equals, but use a custom starting scope. Note that
         | 
| 431 431 | 
             
                  # the query would find "user_3" *except* there is no first name defined
         | 
| @@ -435,7 +435,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 435 435 | 
             
                  query = @instance.to_activerecord_query(MockUser.where.not('first_name' => 'John'))
         | 
| 436 436 |  | 
| 437 437 | 
             
                  expect(query.count).to eql(1)
         | 
| 438 | 
            -
                  expect(query.pluck(: | 
| 438 | 
            +
                  expect(query.pluck(:id)).to match_array([user_1.id])
         | 
| 439 439 | 
             
                end
         | 
| 440 440 |  | 
| 441 441 | 
             
                context 'when mapped to multiple columns' do
         | 
| @@ -481,66 +481,6 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 481 481 | 
             
                  end
         | 
| 482 482 | 
             
                end # "context 'when instructed to ignore an attribute' do"
         | 
| 483 483 |  | 
| 484 | 
            -
                context 'when an arel column is mapped' do
         | 
| 485 | 
            -
                  let(:scope_with_groups) { MockUser.left_joins(:mock_groups) }
         | 
| 486 | 
            -
             | 
| 487 | 
            -
                  context 'with binary operators' do
         | 
| 488 | 
            -
                    it 'reads across all using OR' do
         | 
| 489 | 
            -
                      @instance.parse('groups eq "12345"')
         | 
| 490 | 
            -
                      query = @instance.to_activerecord_query(scope_with_groups)
         | 
| 491 | 
            -
             | 
| 492 | 
            -
                      expect(query.to_sql).to eql(<<~SQL.squish)
         | 
| 493 | 
            -
                        SELECT "mock_users".*
         | 
| 494 | 
            -
                        FROM "mock_users"
         | 
| 495 | 
            -
                          LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
         | 
| 496 | 
            -
                          LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
         | 
| 497 | 
            -
                        WHERE "mock_groups"."id" ILIKE 12345
         | 
| 498 | 
            -
                      SQL
         | 
| 499 | 
            -
                    end
         | 
| 500 | 
            -
             | 
| 501 | 
            -
                    it 'works with other query elements using correct precedence' do
         | 
| 502 | 
            -
                      @instance.parse('groups eq "12345" and emails eq "any@test.com"')
         | 
| 503 | 
            -
                      query = @instance.to_activerecord_query(scope_with_groups)
         | 
| 504 | 
            -
             | 
| 505 | 
            -
                      expect(query.to_sql).to eql(<<~SQL.squish)
         | 
| 506 | 
            -
                        SELECT "mock_users".*
         | 
| 507 | 
            -
                        FROM "mock_users"
         | 
| 508 | 
            -
                          LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
         | 
| 509 | 
            -
                          LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
         | 
| 510 | 
            -
                        WHERE "mock_groups"."id" ILIKE 12345 AND ("mock_users"."work_email_address" ILIKE 'any@test.com' OR "mock_users"."home_email_address" ILIKE 'any@test.com')
         | 
| 511 | 
            -
                      SQL
         | 
| 512 | 
            -
                    end
         | 
| 513 | 
            -
                  end # "context 'with binary operators' do"
         | 
| 514 | 
            -
             | 
| 515 | 
            -
                  context 'with unary operators' do
         | 
| 516 | 
            -
                    it 'reads across all using OR' do
         | 
| 517 | 
            -
                      @instance.parse('groups pr')
         | 
| 518 | 
            -
                      query = @instance.to_activerecord_query(scope_with_groups)
         | 
| 519 | 
            -
             | 
| 520 | 
            -
                      expect(query.to_sql).to eql(<<~SQL.squish)
         | 
| 521 | 
            -
                        SELECT "mock_users".*
         | 
| 522 | 
            -
                        FROM "mock_users"
         | 
| 523 | 
            -
                          LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
         | 
| 524 | 
            -
                          LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
         | 
| 525 | 
            -
                        WHERE ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
         | 
| 526 | 
            -
                      SQL
         | 
| 527 | 
            -
                    end
         | 
| 528 | 
            -
             | 
| 529 | 
            -
                    it 'works with other query elements using correct precedence' do
         | 
| 530 | 
            -
                      @instance.parse('name.familyName eq "John" and groups pr')
         | 
| 531 | 
            -
                      query = @instance.to_activerecord_query(scope_with_groups)
         | 
| 532 | 
            -
             | 
| 533 | 
            -
                      expect(query.to_sql).to eql(<<~SQL.squish)
         | 
| 534 | 
            -
                        SELECT "mock_users".*
         | 
| 535 | 
            -
                        FROM "mock_users"
         | 
| 536 | 
            -
                          LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
         | 
| 537 | 
            -
                          LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
         | 
| 538 | 
            -
                        WHERE "mock_users"."last_name" ILIKE 'John' AND ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
         | 
| 539 | 
            -
                      SQL
         | 
| 540 | 
            -
                    end
         | 
| 541 | 
            -
                  end # "context 'with unary operators' do
         | 
| 542 | 
            -
                end # "context 'when an arel column is mapped' do"
         | 
| 543 | 
            -
             | 
| 544 484 | 
             
                context 'with complex cases' do
         | 
| 545 485 | 
             
                  context 'using AND' do
         | 
| 546 486 | 
             
                    it 'generates expected SQL' do
         | 
| @@ -559,7 +499,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 559 499 | 
             
                      query = @instance.to_activerecord_query(MockUser.all)
         | 
| 560 500 |  | 
| 561 501 | 
             
                      expect(query.count).to eql(1)
         | 
| 562 | 
            -
                      expect(query.pluck(: | 
| 502 | 
            +
                      expect(query.pluck(:id)).to match_array([user_2.id])
         | 
| 563 503 | 
             
                    end
         | 
| 564 504 | 
             
                  end # "context 'simple AND' do"
         | 
| 565 505 |  | 
| @@ -580,7 +520,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 580 520 | 
             
                      query = @instance.to_activerecord_query(MockUser.all)
         | 
| 581 521 |  | 
| 582 522 | 
             
                      expect(query.count).to eql(2)
         | 
| 583 | 
            -
                      expect(query.pluck(: | 
| 523 | 
            +
                      expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
         | 
| 584 524 | 
             
                    end
         | 
| 585 525 | 
             
                  end # "context 'simple OR' do"
         | 
| 586 526 |  | 
| @@ -592,13 +532,6 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 592 532 | 
             
                      expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE "mock_users"."first_name" ILIKE 'Jane' AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
         | 
| 593 533 | 
             
                    end
         | 
| 594 534 |  | 
| 595 | 
            -
                    it 'combined parentheses generates expected SQL' do
         | 
| 596 | 
            -
                      @instance.parse('(name.givenName eq "Jane" OR name.givenName eq "Jaden") and (name.familyName co "avi" or name.familyName ew "ith")')
         | 
| 597 | 
            -
                      query = @instance.to_activerecord_query(MockUser.all)
         | 
| 598 | 
            -
             | 
| 599 | 
            -
                      expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE ("mock_users"."first_name" ILIKE 'Jane' OR "mock_users"."first_name" ILIKE 'Jaden') AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
         | 
| 600 | 
            -
                    end
         | 
| 601 | 
            -
             | 
| 602 535 | 
             
                    it 'finds expected items' do
         | 
| 603 536 | 
             
                      user_1 = MockUser.create(username: '1', first_name: 'Jane', last_name: 'Davis')   # Match
         | 
| 604 537 | 
             
                      user_2 = MockUser.create(username: '2', first_name: 'Jane', last_name: 'Smith')   # Match
         | 
| @@ -613,7 +546,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 613 546 | 
             
                      query = @instance.to_activerecord_query(MockUser.all)
         | 
| 614 547 |  | 
| 615 548 | 
             
                      expect(query.count).to eql(3)
         | 
| 616 | 
            -
                      expect(query.pluck(: | 
| 549 | 
            +
                      expect(query.pluck(:id)).to match_array([user_1.id, user_2.id, user_3.id])
         | 
| 617 550 | 
             
                    end
         | 
| 618 551 | 
             
                  end # "context 'combined AND and OR' do"
         | 
| 619 552 |  | 
| @@ -657,7 +590,7 @@ RSpec.describe Scimitar::Lists::QueryParser do | |
| 657 590 | 
             
                  end
         | 
| 658 591 |  | 
| 659 592 | 
             
                  it 'complains if there is no column mapping available' do
         | 
| 660 | 
            -
                    expect { @instance.send(:activerecord_columns, ' | 
| 593 | 
            +
                    expect { @instance.send(:activerecord_columns, 'externalId') }.to raise_error(Scimitar::FilterError)
         | 
| 661 594 | 
             
                  end
         | 
| 662 595 |  | 
| 663 596 | 
             
                  it 'complains about malformed declarations' do
         |