scimitar 1.3.3 → 1.5.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 +27 -7
- data/app/models/scimitar/resources/mixin.rb +73 -1
- data/lib/scimitar/version.rb +2 -2
- 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 +3 -3
- data/spec/apps/dummy/config/routes.rb +4 -0
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +2 -2
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +8 -3
- data/spec/apps/dummy/db/schema.rb +9 -7
- data/spec/models/scimitar/lists/query_parser_spec.rb +8 -8
- data/spec/models/scimitar/resources/mixin_spec.rb +657 -40
- data/spec/models/scimitar/resources/user_spec.rb +2 -2
- data/spec/requests/active_record_backed_resources_controller_spec.rb +232 -48
- metadata +35 -37
- data/spec/apps/dummy/db/migrate/20230109012729_add_timestamps_to_mock_user.rb +0 -5
| @@ -42,13 +42,13 @@ RSpec.describe Scimitar::Resources::User do | |
| 42 42 | 
             
                let(:user) { described_class.new }
         | 
| 43 43 |  | 
| 44 44 | 
             
                it 'adds the error when the value is a string' do
         | 
| 45 | 
            -
                  user.add_errors_from_hash(key: 'some error')
         | 
| 45 | 
            +
                  user.add_errors_from_hash({key: 'some error'})
         | 
| 46 46 | 
             
                  expect(user.errors.messages.to_h).to eql({key: ['some error']})
         | 
| 47 47 | 
             
                  expect(user.errors.full_messages).to eql(['Key some error'])
         | 
| 48 48 | 
             
                end
         | 
| 49 49 |  | 
| 50 50 | 
             
                it 'adds the error when the value is an array' do
         | 
| 51 | 
            -
                  user.add_errors_from_hash(key: ['error1', 'error2'])
         | 
| 51 | 
            +
                  user.add_errors_from_hash({key: ['error1', 'error2']})
         | 
| 52 52 | 
             
                  expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
         | 
| 53 53 | 
             
                  expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
         | 
| 54 54 | 
             
                end
         | 
| @@ -7,9 +7,21 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 7 7 |  | 
| 8 8 | 
             
                lmt = Time.parse("2023-01-09 14:25:00 +1300")
         | 
| 9 9 |  | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 12 | 
            -
                 | 
| 10 | 
            +
                # If a sort order is unspecified, the controller defaults to ID ascending.
         | 
| 11 | 
            +
                # With UUID based IDs, testing life is made easier by ensuring that the
         | 
| 12 | 
            +
                # creation order matches an ascending UUID sort order (which is what would
         | 
| 13 | 
            +
                # happen if we were using integer primary keys).
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                lmt = Time.parse("2023-01-09 14:25:00 +1300")
         | 
| 16 | 
            +
                ids = 3.times.map { SecureRandom.uuid }.sort()
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                @u1 = MockUser.create(primary_key: ids.shift(), username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
         | 
| 19 | 
            +
                @u2 = MockUser.create(primary_key: ids.shift(), username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
         | 
| 20 | 
            +
                @u3 = MockUser.create(primary_key: ids.shift(), username: '3', first_name: 'Foo',                   home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                @g1 = MockGroup.create!(display_name: 'Group 1')
         | 
| 23 | 
            +
                @g2 = MockGroup.create!(display_name: 'Group 2')
         | 
| 24 | 
            +
                @g3 = MockGroup.create!(display_name: 'Group 3')
         | 
| 13 25 | 
             
              end
         | 
| 14 26 |  | 
| 15 27 | 
             
              # ===========================================================================
         | 
| @@ -32,21 +44,41 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 32 44 | 
             
                end # "context 'with no items' do"
         | 
| 33 45 |  | 
| 34 46 | 
             
                context 'with items' do
         | 
| 35 | 
            -
                   | 
| 36 | 
            -
                     | 
| 47 | 
            +
                  context 'with a UUID, renamed primary key column' do
         | 
| 48 | 
            +
                    it 'returns all items' do
         | 
| 49 | 
            +
                      get '/Users', params: { format: :scim }
         | 
| 37 50 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 51 | 
            +
                      expect(response.status).to eql(200)
         | 
| 52 | 
            +
                      result = JSON.parse(response.body)
         | 
| 40 53 |  | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 54 | 
            +
                      expect(result['totalResults']).to eql(3)
         | 
| 55 | 
            +
                      expect(result['Resources'].size).to eql(3)
         | 
| 43 56 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 57 | 
            +
                      ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 58 | 
            +
                      expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
         | 
| 46 59 |  | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 60 | 
            +
                      usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 61 | 
            +
                      expect(usernames).to match_array(['1', '2', '3'])
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end # "context 'with a UUID, renamed primary key column' do"
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  context 'with an integer, conventionally named primary key column' do
         | 
| 66 | 
            +
                    it 'returns all items' do
         | 
| 67 | 
            +
                      get '/Groups', params: { format: :scim }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      expect(response.status).to eql(200)
         | 
| 70 | 
            +
                      result = JSON.parse(response.body)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      expect(result['totalResults']).to eql(3)
         | 
| 73 | 
            +
                      expect(result['Resources'].size).to eql(3)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 76 | 
            +
                      expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      usernames = result['Resources'].map { |resource| resource['displayName'] }
         | 
| 79 | 
            +
                      expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                  end # "context 'with an integer, conventionally named primary key column' do"
         | 
| 50 82 |  | 
| 51 83 | 
             
                  it 'applies a filter, with case-insensitive value comparison' do
         | 
| 52 84 | 
             
                    get '/Users', params: {
         | 
| @@ -61,7 +93,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 61 93 | 
             
                    expect(result['Resources'].size).to eql(1)
         | 
| 62 94 |  | 
| 63 95 | 
             
                    ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 64 | 
            -
                    expect(ids).to match_array([@u2. | 
| 96 | 
            +
                    expect(ids).to match_array([@u2.primary_key.to_s])
         | 
| 65 97 |  | 
| 66 98 | 
             
                    usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 67 99 | 
             
                    expect(usernames).to match_array(['2'])
         | 
| @@ -80,7 +112,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 80 112 | 
             
                    expect(result['Resources'].size).to eql(1)
         | 
| 81 113 |  | 
| 82 114 | 
             
                    ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 83 | 
            -
                    expect(ids).to match_array([@u2. | 
| 115 | 
            +
                    expect(ids).to match_array([@u2.primary_key.to_s])
         | 
| 84 116 |  | 
| 85 117 | 
             
                    usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 86 118 | 
             
                    expect(usernames).to match_array(['2'])
         | 
| @@ -93,7 +125,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 93 125 | 
             
                    it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
         | 
| 94 126 | 
             
                      get '/Users', params: {
         | 
| 95 127 | 
             
                        format: :scim,
         | 
| 96 | 
            -
                        filter: "id eq \"#{@u3. | 
| 128 | 
            +
                        filter: "id eq \"#{@u3.primary_key}\""
         | 
| 97 129 | 
             
                      }
         | 
| 98 130 |  | 
| 99 131 | 
             
                      expect(response.status).to eql(200)
         | 
| @@ -103,7 +135,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 103 135 | 
             
                      expect(result['Resources'].size).to eql(1)
         | 
| 104 136 |  | 
| 105 137 | 
             
                      ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 106 | 
            -
                      expect(ids).to match_array([@u3. | 
| 138 | 
            +
                      expect(ids).to match_array([@u3.primary_key.to_s])
         | 
| 107 139 |  | 
| 108 140 | 
             
                      usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 109 141 | 
             
                      expect(usernames).to match_array(['3'])
         | 
| @@ -122,7 +154,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 122 154 | 
             
                      expect(result['Resources'].size).to eql(1)
         | 
| 123 155 |  | 
| 124 156 | 
             
                      ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 125 | 
            -
                      expect(ids).to match_array([@u2. | 
| 157 | 
            +
                      expect(ids).to match_array([@u2.primary_key.to_s])
         | 
| 126 158 |  | 
| 127 159 | 
             
                      usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 128 160 | 
             
                      expect(usernames).to match_array(['2'])
         | 
| @@ -141,7 +173,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 141 173 | 
             
                      expect(result['Resources'].size).to eql(1)
         | 
| 142 174 |  | 
| 143 175 | 
             
                      ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 144 | 
            -
                      expect(ids).to match_array([@u3. | 
| 176 | 
            +
                      expect(ids).to match_array([@u3.primary_key.to_s])
         | 
| 145 177 |  | 
| 146 178 | 
             
                      usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 147 179 | 
             
                      expect(usernames).to match_array(['3'])
         | 
| @@ -161,7 +193,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 161 193 | 
             
                    expect(result['Resources'].size).to eql(2)
         | 
| 162 194 |  | 
| 163 195 | 
             
                    ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 164 | 
            -
                    expect(ids).to match_array([@u1. | 
| 196 | 
            +
                    expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s])
         | 
| 165 197 |  | 
| 166 198 | 
             
                    usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 167 199 | 
             
                    expect(usernames).to match_array(['1', '2'])
         | 
| @@ -180,7 +212,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 180 212 | 
             
                    expect(result['Resources'].size).to eql(2)
         | 
| 181 213 |  | 
| 182 214 | 
             
                    ids = result['Resources'].map { |resource| resource['id'] }
         | 
| 183 | 
            -
                    expect(ids).to match_array([@u2. | 
| 215 | 
            +
                    expect(ids).to match_array([@u2.primary_key.to_s, @u3.primary_key.to_s])
         | 
| 184 216 |  | 
| 185 217 | 
             
                    usernames = result['Resources'].map { |resource| resource['userName'] }
         | 
| 186 218 | 
             
                    expect(usernames).to match_array(['2', '3'])
         | 
| @@ -204,18 +236,34 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 204 236 | 
             
              # ===========================================================================
         | 
| 205 237 |  | 
| 206 238 | 
             
              context '#show' do
         | 
| 207 | 
            -
                 | 
| 208 | 
            -
                   | 
| 209 | 
            -
             | 
| 239 | 
            +
                context 'with a UUID, renamed primary key column' do
         | 
| 240 | 
            +
                  it 'shows an item' do
         | 
| 241 | 
            +
                    expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
         | 
| 242 | 
            +
                    get "/Users/#{@u2.primary_key}", params: { format: :scim }
         | 
| 210 243 |  | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 244 | 
            +
                    expect(response.status).to eql(200)
         | 
| 245 | 
            +
                    result = JSON.parse(response.body)
         | 
| 213 246 |  | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 247 | 
            +
                    expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 248 | 
            +
                    expect(result['userName']).to eql('2')
         | 
| 249 | 
            +
                    expect(result['name']['familyName']).to eql('Bar')
         | 
| 250 | 
            +
                    expect(result['meta']['resourceType']).to eql('User')
         | 
| 251 | 
            +
                  end
         | 
| 252 | 
            +
                end # "context 'with a UUID, renamed primary key column' do"
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                context 'with an integer, conventionally named primary key column' do
         | 
| 255 | 
            +
                  it 'shows an item' do
         | 
| 256 | 
            +
                    expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
         | 
| 257 | 
            +
                    get "/Groups/#{@g2.id}", params: { format: :scim }
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                    expect(response.status).to eql(200)
         | 
| 260 | 
            +
                    result = JSON.parse(response.body)
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                    expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
         | 
| 263 | 
            +
                    expect(result['displayName']).to eql('Group 2')
         | 
| 264 | 
            +
                    expect(result['meta']['resourceType']).to eql('Group')
         | 
| 265 | 
            +
                  end
         | 
| 266 | 
            +
                end # "context 'with an integer, conventionally named primary key column' do"
         | 
| 219 267 |  | 
| 220 268 | 
             
                it 'renders 404' do
         | 
| 221 269 | 
             
                  get '/Users/xyz', params: { format: :scim }
         | 
| @@ -248,7 +296,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 248 296 | 
             
                      expect(response.status).to eql(201)
         | 
| 249 297 | 
             
                      result = JSON.parse(response.body)
         | 
| 250 298 |  | 
| 251 | 
            -
                      expect(result['id']).to eql(new_mock. | 
| 299 | 
            +
                      expect(result['id']).to eql(new_mock.primary_key.to_s)
         | 
| 252 300 | 
             
                      expect(result['meta']['resourceType']).to eql('User')
         | 
| 253 301 | 
             
                      expect(new_mock.username).to eql('4')
         | 
| 254 302 | 
             
                    end
         | 
| @@ -289,7 +337,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 289 337 | 
             
                      expect(response.status).to eql(201)
         | 
| 290 338 | 
             
                      result = JSON.parse(response.body)
         | 
| 291 339 |  | 
| 292 | 
            -
                      expect(result['id']).to eql(new_mock. | 
| 340 | 
            +
                      expect(result['id']).to eql(new_mock.primary_key.to_s)
         | 
| 293 341 | 
             
                      expect(result['meta']['resourceType']).to eql('User')
         | 
| 294 342 | 
             
                      expect(new_mock.username).to eql('4')
         | 
| 295 343 | 
             
                      expect(new_mock.first_name).to eql('Given')
         | 
| @@ -363,13 +411,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 363 411 |  | 
| 364 412 | 
             
                    expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
         | 
| 365 413 | 
             
                    expect {
         | 
| 366 | 
            -
                      put "/Users/#{@u2. | 
| 414 | 
            +
                      put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
         | 
| 367 415 | 
             
                    }.to_not change { MockUser.count }
         | 
| 368 416 |  | 
| 369 417 | 
             
                    expect(response.status).to eql(200)
         | 
| 370 418 | 
             
                    result = JSON.parse(response.body)
         | 
| 371 419 |  | 
| 372 | 
            -
                    expect(result['id']).to eql(@u2. | 
| 420 | 
            +
                    expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 373 421 | 
             
                    expect(result['meta']['resourceType']).to eql('User')
         | 
| 374 422 |  | 
| 375 423 | 
             
                    @u2.reload
         | 
| @@ -391,7 +439,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 391 439 |  | 
| 392 440 | 
             
                it 'notes schema validation failures' do
         | 
| 393 441 | 
             
                  expect {
         | 
| 394 | 
            -
                    put "/Users/#{@u2. | 
| 442 | 
            +
                    put "/Users/#{@u2.primary_key}", params: {
         | 
| 395 443 | 
             
                      format: :scim
         | 
| 396 444 | 
             
                      # userName parameter is required by schema, but missing
         | 
| 397 445 | 
             
                    }
         | 
| @@ -470,13 +518,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 470 518 |  | 
| 471 519 | 
             
                    expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
         | 
| 472 520 | 
             
                    expect {
         | 
| 473 | 
            -
                      patch "/Users/#{@u2. | 
| 521 | 
            +
                      patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
         | 
| 474 522 | 
             
                    }.to_not change { MockUser.count }
         | 
| 475 523 |  | 
| 476 524 | 
             
                    expect(response.status).to eql(200)
         | 
| 477 525 | 
             
                    result = JSON.parse(response.body)
         | 
| 478 526 |  | 
| 479 | 
            -
                    expect(result['id']).to eql(@u2. | 
| 527 | 
            +
                    expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 480 528 | 
             
                    expect(result['meta']['resourceType']).to eql('User')
         | 
| 481 529 |  | 
| 482 530 | 
             
                    @u2.reload
         | 
| @@ -507,13 +555,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 507 555 |  | 
| 508 556 | 
             
                      expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
         | 
| 509 557 | 
             
                      expect {
         | 
| 510 | 
            -
                        patch "/Users/#{@u2. | 
| 558 | 
            +
                        patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
         | 
| 511 559 | 
             
                      }.to_not change { MockUser.count }
         | 
| 512 560 |  | 
| 513 561 | 
             
                      expect(response.status).to eql(200)
         | 
| 514 562 | 
             
                      result = JSON.parse(response.body)
         | 
| 515 563 |  | 
| 516 | 
            -
                      expect(result['id']).to eql(@u2. | 
| 564 | 
            +
                      expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 517 565 | 
             
                      expect(result['meta']['resourceType']).to eql('User')
         | 
| 518 566 |  | 
| 519 567 | 
             
                      @u2.reload
         | 
| @@ -539,13 +587,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 539 587 |  | 
| 540 588 | 
             
                      expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
         | 
| 541 589 | 
             
                      expect {
         | 
| 542 | 
            -
                        patch "/Users/#{@u2. | 
| 590 | 
            +
                        patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
         | 
| 543 591 | 
             
                      }.to_not change { MockUser.count }
         | 
| 544 592 |  | 
| 545 593 | 
             
                      expect(response.status).to eql(200)
         | 
| 546 594 | 
             
                      result = JSON.parse(response.body)
         | 
| 547 595 |  | 
| 548 | 
            -
                      expect(result['id']).to eql(@u2. | 
| 596 | 
            +
                      expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 549 597 | 
             
                      expect(result['meta']['resourceType']).to eql('User')
         | 
| 550 598 |  | 
| 551 599 | 
             
                      @u2.reload
         | 
| @@ -571,13 +619,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 571 619 |  | 
| 572 620 | 
             
                      expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
         | 
| 573 621 | 
             
                      expect {
         | 
| 574 | 
            -
                        patch "/Users/#{@u2. | 
| 622 | 
            +
                        patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
         | 
| 575 623 | 
             
                      }.to_not change { MockUser.count }
         | 
| 576 624 |  | 
| 577 625 | 
             
                      expect(response.status).to eql(200)
         | 
| 578 626 | 
             
                      result = JSON.parse(response.body)
         | 
| 579 627 |  | 
| 580 | 
            -
                      expect(result['id']).to eql(@u2. | 
| 628 | 
            +
                      expect(result['id']).to eql(@u2.primary_key.to_s)
         | 
| 581 629 | 
             
                      expect(result['meta']['resourceType']).to eql('User')
         | 
| 582 630 |  | 
| 583 631 | 
             
                      @u2.reload
         | 
| @@ -601,7 +649,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 601 649 |  | 
| 602 650 | 
             
                it 'notes Rails validation failures' do
         | 
| 603 651 | 
             
                  expect {
         | 
| 604 | 
            -
                    patch "/Users/#{@u2. | 
| 652 | 
            +
                    patch "/Users/#{@u2.primary_key}", params: {
         | 
| 605 653 | 
             
                      format: :scim,
         | 
| 606 654 | 
             
                      Operations: [
         | 
| 607 655 | 
             
                        {
         | 
| @@ -645,6 +693,142 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 645 693 | 
             
                  result = JSON.parse(response.body)
         | 
| 646 694 | 
             
                  expect(result['status']).to eql('404')
         | 
| 647 695 | 
             
                end
         | 
| 696 | 
            +
             | 
| 697 | 
            +
                context 'when removing users from groups' do
         | 
| 698 | 
            +
                  before :each do
         | 
| 699 | 
            +
                    @g1.mock_users << @u1
         | 
| 700 | 
            +
                    @g1.mock_users << @u2
         | 
| 701 | 
            +
                    @g1.mock_users << @u3
         | 
| 702 | 
            +
             | 
| 703 | 
            +
                    # (Self-check) Verify group representation
         | 
| 704 | 
            +
                    #
         | 
| 705 | 
            +
                    get "/Groups/#{@g1.id}", params: { format: :scim }
         | 
| 706 | 
            +
             | 
| 707 | 
            +
                    expect(response.status).to eql(200)
         | 
| 708 | 
            +
                    result = JSON.parse(response.body)
         | 
| 709 | 
            +
             | 
| 710 | 
            +
                    expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
         | 
| 711 | 
            +
                  end
         | 
| 712 | 
            +
             | 
| 713 | 
            +
                  it 'can remove all users' do
         | 
| 714 | 
            +
                    expect {
         | 
| 715 | 
            +
                      expect {
         | 
| 716 | 
            +
                        patch "/Groups/#{@g1.id}", params: {
         | 
| 717 | 
            +
                          format: :scim,
         | 
| 718 | 
            +
                          Operations: [
         | 
| 719 | 
            +
                            {
         | 
| 720 | 
            +
                              op: 'remove',
         | 
| 721 | 
            +
                              path: 'members'
         | 
| 722 | 
            +
                            }
         | 
| 723 | 
            +
                          ]
         | 
| 724 | 
            +
                        }
         | 
| 725 | 
            +
                      }.to_not change { MockUser.count }
         | 
| 726 | 
            +
                    }.to_not change { MockGroup.count }
         | 
| 727 | 
            +
             | 
| 728 | 
            +
                    get "/Groups/#{@g1.id}", params: { format: :scim }
         | 
| 729 | 
            +
             | 
| 730 | 
            +
                    expect(response.status).to eql(200)
         | 
| 731 | 
            +
                    result = JSON.parse(response.body)
         | 
| 732 | 
            +
             | 
| 733 | 
            +
                    expect(result['members']).to be_empty
         | 
| 734 | 
            +
                    expect(@g1.reload().mock_users).to be_empty
         | 
| 735 | 
            +
                  end
         | 
| 736 | 
            +
             | 
| 737 | 
            +
                  # Define via 'let':
         | 
| 738 | 
            +
                  #
         | 
| 739 | 
            +
                  # * Hash 'payload', to send via 'patch'
         | 
| 740 | 
            +
                  # * MockUser 'removed_user', which is the user that should be removed
         | 
| 741 | 
            +
                  #
         | 
| 742 | 
            +
                  shared_examples 'a user remover' do
         | 
| 743 | 
            +
                    it 'which removes the identified user' do
         | 
| 744 | 
            +
                      expect {
         | 
| 745 | 
            +
                        expect {
         | 
| 746 | 
            +
                          patch "/Groups/#{@g1.id}", params: payload()
         | 
| 747 | 
            +
                        }.to_not change { MockUser.count }
         | 
| 748 | 
            +
                      }.to_not change { MockGroup.count }
         | 
| 749 | 
            +
             | 
| 750 | 
            +
                      expected_remaining_user_ids = MockUser
         | 
| 751 | 
            +
                        .where.not(primary_key: removed_user().id)
         | 
| 752 | 
            +
                        .pluck(:primary_key)
         | 
| 753 | 
            +
                        .sort()
         | 
| 754 | 
            +
             | 
| 755 | 
            +
                      get "/Groups/#{@g1.id}", params: { format: :scim }
         | 
| 756 | 
            +
             | 
| 757 | 
            +
                      expect(response.status).to eql(200)
         | 
| 758 | 
            +
                      result = JSON.parse(response.body)
         | 
| 759 | 
            +
             | 
| 760 | 
            +
                      expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
         | 
| 761 | 
            +
                      expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
         | 
| 762 | 
            +
                    end
         | 
| 763 | 
            +
                  end
         | 
| 764 | 
            +
             | 
| 765 | 
            +
                  # https://www.rfc-editor.org/rfc/rfc7644#section-3.5.2.2
         | 
| 766 | 
            +
                  #
         | 
| 767 | 
            +
                  context 'and using an RFC-compliant payload' do
         | 
| 768 | 
            +
                    let(:removed_user) { @u2 }
         | 
| 769 | 
            +
                    let(:payload) do
         | 
| 770 | 
            +
                      {
         | 
| 771 | 
            +
                        format: :scim,
         | 
| 772 | 
            +
                        Operations: [
         | 
| 773 | 
            +
                          {
         | 
| 774 | 
            +
                            op: 'remove',
         | 
| 775 | 
            +
                            path: "members[value eq \"#{removed_user().primary_key}\"]",
         | 
| 776 | 
            +
                          }
         | 
| 777 | 
            +
                        ]
         | 
| 778 | 
            +
                      }
         | 
| 779 | 
            +
                    end
         | 
| 780 | 
            +
             | 
| 781 | 
            +
                    it_behaves_like 'a user remover'
         | 
| 782 | 
            +
                  end # context 'and using an RFC-compliant payload' do
         | 
| 783 | 
            +
             | 
| 784 | 
            +
                  # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
         | 
| 785 | 
            +
                  #
         | 
| 786 | 
            +
                  context 'and using a Microsoft variant payload' do
         | 
| 787 | 
            +
                    let(:removed_user) { @u2 }
         | 
| 788 | 
            +
                    let(:payload) do
         | 
| 789 | 
            +
                      {
         | 
| 790 | 
            +
                        format: :scim,
         | 
| 791 | 
            +
                        Operations: [
         | 
| 792 | 
            +
                          {
         | 
| 793 | 
            +
                            op: 'remove',
         | 
| 794 | 
            +
                            path: 'members',
         | 
| 795 | 
            +
                            value: [{
         | 
| 796 | 
            +
                              '$ref' => nil,
         | 
| 797 | 
            +
                              'value' => removed_user().primary_key
         | 
| 798 | 
            +
                            }]
         | 
| 799 | 
            +
                          }
         | 
| 800 | 
            +
                        ]
         | 
| 801 | 
            +
                      }
         | 
| 802 | 
            +
                    end
         | 
| 803 | 
            +
             | 
| 804 | 
            +
                    it_behaves_like 'a user remover'
         | 
| 805 | 
            +
                  end # context 'and using a Microsoft variant payload' do
         | 
| 806 | 
            +
             | 
| 807 | 
            +
                  # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
         | 
| 808 | 
            +
                  #
         | 
| 809 | 
            +
                  context 'and using a Salesforce variant payload' do
         | 
| 810 | 
            +
                    let(:removed_user) { @u2 }
         | 
| 811 | 
            +
                    let(:payload) do
         | 
| 812 | 
            +
                      {
         | 
| 813 | 
            +
                        format: :scim,
         | 
| 814 | 
            +
                        Operations: [
         | 
| 815 | 
            +
                          {
         | 
| 816 | 
            +
                            op: 'remove',
         | 
| 817 | 
            +
                            path: 'members',
         | 
| 818 | 
            +
                            value: {
         | 
| 819 | 
            +
                              'members' => [{
         | 
| 820 | 
            +
                                '$ref' => nil,
         | 
| 821 | 
            +
                                'value' => removed_user().primary_key
         | 
| 822 | 
            +
                              }]
         | 
| 823 | 
            +
                            }
         | 
| 824 | 
            +
                          }
         | 
| 825 | 
            +
                        ]
         | 
| 826 | 
            +
                      }
         | 
| 827 | 
            +
                    end
         | 
| 828 | 
            +
             | 
| 829 | 
            +
                    it_behaves_like 'a user remover'
         | 
| 830 | 
            +
                  end # context 'and using a Salesforce variant payload' do
         | 
| 831 | 
            +
                end # "context 'when removing users from groups' do"
         | 
| 648 832 | 
             
              end # "context '#update' do"
         | 
| 649 833 |  | 
| 650 834 | 
             
              # ===========================================================================
         | 
| @@ -654,7 +838,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 654 838 | 
             
                  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
         | 
| 655 839 | 
             
                  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
         | 
| 656 840 | 
             
                  expect {
         | 
| 657 | 
            -
                    delete "/Users/#{@u2. | 
| 841 | 
            +
                    delete "/Users/#{@u2.primary_key}", params: { format: :scim }
         | 
| 658 842 | 
             
                  }.to change { MockUser.count }.by(-1)
         | 
| 659 843 |  | 
| 660 844 | 
             
                  expect(response.status).to eql(204)
         | 
| @@ -666,7 +850,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 666 850 | 
             
                  expect_any_instance_of(MockUser).to_not receive(:destroy!)
         | 
| 667 851 |  | 
| 668 852 | 
             
                  expect {
         | 
| 669 | 
            -
                    delete "/CustomDestroyUsers/#{@u2. | 
| 853 | 
            +
                    delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
         | 
| 670 854 | 
             
                  }.to_not change { MockUser.count }
         | 
| 671 855 |  | 
| 672 856 | 
             
                  expect(response.status).to eql(204)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: scimitar
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.5.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - RIPA Global
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire:
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2023-01- | 
| 12 | 
            +
            date: 2023-01-27 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rails
         | 
| @@ -211,7 +211,6 @@ files: | |
| 211 211 | 
             
            - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb
         | 
| 212 212 | 
             
            - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
         | 
| 213 213 | 
             
            - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
         | 
| 214 | 
            -
            - spec/apps/dummy/db/migrate/20230109012729_add_timestamps_to_mock_user.rb
         | 
| 215 214 | 
             
            - spec/apps/dummy/db/schema.rb
         | 
| 216 215 | 
             
            - spec/controllers/scimitar/application_controller_spec.rb
         | 
| 217 216 | 
             
            - spec/controllers/scimitar/resource_types_controller_spec.rb
         | 
| @@ -261,52 +260,51 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 261 260 | 
             
                - !ruby/object:Gem::Version
         | 
| 262 261 | 
             
                  version: '0'
         | 
| 263 262 | 
             
            requirements: []
         | 
| 264 | 
            -
            rubygems_version: 3. | 
| 263 | 
            +
            rubygems_version: 3.4.4
         | 
| 265 264 | 
             
            signing_key:
         | 
| 266 265 | 
             
            specification_version: 4
         | 
| 267 266 | 
             
            summary: SCIM v2 for Rails
         | 
| 268 267 | 
             
            test_files:
         | 
| 269 | 
            -
            - spec/ | 
| 270 | 
            -
            - spec/ | 
| 268 | 
            +
            - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
         | 
| 269 | 
            +
            - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
         | 
| 270 | 
            +
            - spec/apps/dummy/app/controllers/mock_groups_controller.rb
         | 
| 271 | 
            +
            - spec/apps/dummy/app/controllers/mock_users_controller.rb
         | 
| 272 | 
            +
            - spec/apps/dummy/app/models/mock_group.rb
         | 
| 273 | 
            +
            - spec/apps/dummy/app/models/mock_user.rb
         | 
| 274 | 
            +
            - spec/apps/dummy/config/application.rb
         | 
| 275 | 
            +
            - spec/apps/dummy/config/boot.rb
         | 
| 276 | 
            +
            - spec/apps/dummy/config/environment.rb
         | 
| 277 | 
            +
            - spec/apps/dummy/config/environments/test.rb
         | 
| 278 | 
            +
            - spec/apps/dummy/config/initializers/cookies_serializer.rb
         | 
| 279 | 
            +
            - spec/apps/dummy/config/initializers/scimitar.rb
         | 
| 280 | 
            +
            - spec/apps/dummy/config/initializers/session_store.rb
         | 
| 281 | 
            +
            - spec/apps/dummy/config/routes.rb
         | 
| 282 | 
            +
            - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb
         | 
| 283 | 
            +
            - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
         | 
| 284 | 
            +
            - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
         | 
| 285 | 
            +
            - spec/apps/dummy/db/schema.rb
         | 
| 286 | 
            +
            - spec/controllers/scimitar/application_controller_spec.rb
         | 
| 287 | 
            +
            - spec/controllers/scimitar/resource_types_controller_spec.rb
         | 
| 288 | 
            +
            - spec/controllers/scimitar/resources_controller_spec.rb
         | 
| 289 | 
            +
            - spec/controllers/scimitar/schemas_controller_spec.rb
         | 
| 290 | 
            +
            - spec/controllers/scimitar/service_provider_configurations_controller_spec.rb
         | 
| 291 | 
            +
            - spec/models/scimitar/complex_types/address_spec.rb
         | 
| 292 | 
            +
            - spec/models/scimitar/complex_types/email_spec.rb
         | 
| 271 293 | 
             
            - spec/models/scimitar/lists/count_spec.rb
         | 
| 294 | 
            +
            - spec/models/scimitar/lists/query_parser_spec.rb
         | 
| 295 | 
            +
            - spec/models/scimitar/resource_type_spec.rb
         | 
| 296 | 
            +
            - spec/models/scimitar/resources/base_spec.rb
         | 
| 272 297 | 
             
            - spec/models/scimitar/resources/base_validation_spec.rb
         | 
| 273 298 | 
             
            - spec/models/scimitar/resources/mixin_spec.rb
         | 
| 274 299 | 
             
            - spec/models/scimitar/resources/user_spec.rb
         | 
| 275 | 
            -
            - spec/models/scimitar/resources/base_spec.rb
         | 
| 276 300 | 
             
            - spec/models/scimitar/schema/attribute_spec.rb
         | 
| 301 | 
            +
            - spec/models/scimitar/schema/base_spec.rb
         | 
| 277 302 | 
             
            - spec/models/scimitar/schema/group_spec.rb
         | 
| 278 303 | 
             
            - spec/models/scimitar/schema/user_spec.rb
         | 
| 279 | 
            -
            - spec/models/scimitar/schema/base_spec.rb
         | 
| 280 | 
            -
            - spec/models/scimitar/resource_type_spec.rb
         | 
| 281 | 
            -
            - spec/models/scimitar/complex_types/email_spec.rb
         | 
| 282 | 
            -
            - spec/models/scimitar/complex_types/address_spec.rb
         | 
| 283 | 
            -
            - spec/requests/controller_configuration_spec.rb
         | 
| 284 | 
            -
            - spec/requests/engine_spec.rb
         | 
| 285 304 | 
             
            - spec/requests/active_record_backed_resources_controller_spec.rb
         | 
| 286 305 | 
             
            - spec/requests/application_controller_spec.rb
         | 
| 306 | 
            +
            - spec/requests/controller_configuration_spec.rb
         | 
| 307 | 
            +
            - spec/requests/engine_spec.rb
         | 
| 308 | 
            +
            - spec/spec_helper.rb
         | 
| 287 309 | 
             
            - spec/spec_helper_spec.rb
         | 
| 288 310 | 
             
            - spec/support/hash_with_indifferent_case_insensitive_access_spec.rb
         | 
| 289 | 
            -
            - spec/controllers/scimitar/schemas_controller_spec.rb
         | 
| 290 | 
            -
            - spec/controllers/scimitar/resource_types_controller_spec.rb
         | 
| 291 | 
            -
            - spec/controllers/scimitar/resources_controller_spec.rb
         | 
| 292 | 
            -
            - spec/controllers/scimitar/service_provider_configurations_controller_spec.rb
         | 
| 293 | 
            -
            - spec/controllers/scimitar/application_controller_spec.rb
         | 
| 294 | 
            -
            - spec/apps/dummy/app/models/mock_group.rb
         | 
| 295 | 
            -
            - spec/apps/dummy/app/models/mock_user.rb
         | 
| 296 | 
            -
            - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
         | 
| 297 | 
            -
            - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
         | 
| 298 | 
            -
            - spec/apps/dummy/app/controllers/mock_groups_controller.rb
         | 
| 299 | 
            -
            - spec/apps/dummy/app/controllers/mock_users_controller.rb
         | 
| 300 | 
            -
            - spec/apps/dummy/config/routes.rb
         | 
| 301 | 
            -
            - spec/apps/dummy/config/environments/test.rb
         | 
| 302 | 
            -
            - spec/apps/dummy/config/environment.rb
         | 
| 303 | 
            -
            - spec/apps/dummy/config/application.rb
         | 
| 304 | 
            -
            - spec/apps/dummy/config/boot.rb
         | 
| 305 | 
            -
            - spec/apps/dummy/config/initializers/session_store.rb
         | 
| 306 | 
            -
            - spec/apps/dummy/config/initializers/cookies_serializer.rb
         | 
| 307 | 
            -
            - spec/apps/dummy/config/initializers/scimitar.rb
         | 
| 308 | 
            -
            - spec/apps/dummy/db/schema.rb
         | 
| 309 | 
            -
            - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
         | 
| 310 | 
            -
            - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
         | 
| 311 | 
            -
            - spec/apps/dummy/db/migrate/20230109012729_add_timestamps_to_mock_user.rb
         | 
| 312 | 
            -
            - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb
         |