scimitar 1.3.3 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 382f2feb979c655de91c1d30bf1fa8bb5347059abe376839758a9b34992f8616
4
- data.tar.gz: d1d8f8c32a92f4b879699a995b4e1746b7b778d782ab3101695e59891a107606
3
+ metadata.gz: cd0efbdecece6297062b46cf46bdeaccd8330cd58fc94fb2bcb0de7c7bb90806
4
+ data.tar.gz: 7f3528ee080f0f295d03c77fba1586484b5cadd6ef5e4de1fc00d7807ec7a0c1
5
5
  SHA512:
6
- metadata.gz: 87373dddf17fe6791e7464c228e688338267bf1a230cd2ab1d69372185ea3adcf165b969872e0e05556ba28b1e9e1581ddaac5639f89235bd7f19da081ff3e7f
7
- data.tar.gz: ae79056e07500c1b195807a9a3f12755895f47a4383c7451208a2956b79ec02e600a8c915fa8a600cccce559c16b5c62f73e081ced2e09e8bee5da63910a2a1e
6
+ metadata.gz: 8044cc09d7b964401b09e6fe8ccfa272b6c67ab97687c79a151010a447109f9e671bb864094905b2495100591e2472f673ce2b977fc07f2a13213a8d4fbc75b1
7
+ data.tar.gz: 51a5522fcf948d3b9f19c6bc5b28732f0fb4537034106d47dca83d7d043d5749d9adf1c31acf10fc7eb79a63739787a0ec42f0a261eb293072634aabca0ec92d
@@ -21,6 +21,8 @@ module Scimitar
21
21
 
22
22
  rescue_from ActiveRecord::RecordNotFound, with: :handle_resource_not_found # See Scimitar::ApplicationController
23
23
 
24
+ before_action :obtain_id_column_name_from_attribute_map
25
+
24
26
  # GET (list)
25
27
  #
26
28
  def index
@@ -37,13 +39,13 @@ module Scimitar
37
39
  pagination_info = scim_pagination_info(query.count())
38
40
 
39
41
  page_of_results = query
40
- .order(id: :asc)
42
+ .order(@id_column => :asc)
41
43
  .offset(pagination_info.offset)
42
44
  .limit(pagination_info.limit)
43
45
  .to_a()
44
46
 
45
47
  super(pagination_info, page_of_results) do | record |
46
- record.to_scim(location: url_for(action: :show, id: record.id))
48
+ record_to_scim(record)
47
49
  end
48
50
  end
49
51
 
@@ -52,7 +54,7 @@ module Scimitar
52
54
  def show
53
55
  super do |record_id|
54
56
  record = self.find_record(record_id)
55
- record.to_scim(location: url_for(action: :show, id: record_id))
57
+ record_to_scim(record)
56
58
  end
57
59
  end
58
60
 
@@ -64,7 +66,7 @@ module Scimitar
64
66
  record = self.storage_class().new
65
67
  record.from_scim!(scim_hash: scim_resource.as_json())
66
68
  self.save!(record)
67
- record.to_scim(location: url_for(action: :show, id: record.id))
69
+ record_to_scim(record)
68
70
  end
69
71
  end
70
72
  end
@@ -77,7 +79,7 @@ module Scimitar
77
79
  record = self.find_record(record_id)
78
80
  record.from_scim!(scim_hash: scim_resource.as_json())
79
81
  self.save!(record)
80
- record.to_scim(location: url_for(action: :show, id: record.id))
82
+ record_to_scim(record)
81
83
  end
82
84
  end
83
85
  end
@@ -90,7 +92,7 @@ module Scimitar
90
92
  record = self.find_record(record_id)
91
93
  record.from_scim_patch!(patch_hash: patch_hash)
92
94
  self.save!(record)
93
- record.to_scim(location: url_for(action: :show, id: record.id))
95
+ record_to_scim(record)
94
96
  end
95
97
  end
96
98
  end
@@ -138,7 +140,14 @@ module Scimitar
138
140
  # +record_id+:: Record ID (SCIM schema 'id' value - "our" ID).
139
141
  #
140
142
  def find_record(record_id)
141
- self.storage_scope().find(record_id)
143
+ self.storage_scope().find_by!(@id_column => record_id)
144
+ end
145
+
146
+ # DRY up controller actions - pass a record; returns the SCIM
147
+ # representation, with a "show" location specified via #url_for.
148
+ #
149
+ def record_to_scim(record)
150
+ record.to_scim(location: url_for(action: :show, id: record.send(@id_column)))
142
151
  end
143
152
 
144
153
  # Save a record, dealing with validation exceptions by raising SCIM
@@ -177,5 +186,16 @@ module Scimitar
177
186
  end
178
187
  end
179
188
 
189
+ # Called via +before_action+ - stores in @id_column the name of whatever
190
+ # model column is used to store the record ID, via
191
+ # Scimitar::Resources::Mixin::scim_attributes_map.
192
+ #
193
+ # Default is <tt>:id</tt>.
194
+ #
195
+ def obtain_id_column_name_from_attribute_map
196
+ attrs = storage_class().scim_attributes_map() || {}
197
+ @id_column = attrs[:id] || :id
198
+ end
199
+
180
200
  end
181
201
  end
@@ -3,11 +3,11 @@ module Scimitar
3
3
  # Gem version. If this changes, be sure to re-run "bundle install" or
4
4
  # "bundle update".
5
5
  #
6
- VERSION = '1.3.3'
6
+ VERSION = '1.4.0'
7
7
 
8
8
  # Date for VERSION. If this changes, be sure to re-run "bundle install"
9
9
  # or "bundle update".
10
10
  #
11
- DATE = '2023-01-10'
11
+ DATE = '2023-01-26'
12
12
 
13
13
  end
@@ -1,4 +1,4 @@
1
- class MockUsersController < Scimitar::ActiveRecordBackedResourcesController
1
+ class MockGroupsController < Scimitar::ActiveRecordBackedResourcesController
2
2
 
3
3
  protected
4
4
 
@@ -58,7 +58,7 @@ class MockGroup < ActiveRecord::Base
58
58
 
59
59
  case type.downcase
60
60
  when 'user'
61
- MockUser.find_by_id(id)
61
+ MockUser.find_by_primary_key(id)
62
62
  when 'group'
63
63
  MockGroup.find_by_id(id)
64
64
  else
@@ -5,7 +5,7 @@ class MockUser < ActiveRecord::Base
5
5
  # ===========================================================================
6
6
 
7
7
  READWRITE_ATTRS = %w{
8
- id
8
+ primary_key
9
9
  scim_uid
10
10
  username
11
11
  first_name
@@ -38,7 +38,7 @@ class MockUser < ActiveRecord::Base
38
38
 
39
39
  def self.scim_attributes_map
40
40
  return {
41
- id: :id,
41
+ id: :primary_key,
42
42
  externalId: :scim_uid,
43
43
  userName: :username,
44
44
  name: {
@@ -92,7 +92,7 @@ class MockUser < ActiveRecord::Base
92
92
 
93
93
  def self.scim_queryable_attributes
94
94
  return {
95
- 'id' => { column: :id },
95
+ 'id' => { column: :primary_key },
96
96
  'externalId' => { column: :scim_uid },
97
97
  'meta.lastModified' => { column: :updated_at },
98
98
  'name.givenName' => { column: :first_name },
@@ -13,6 +13,9 @@ Rails.application.routes.draw do
13
13
  patch 'Users/:id', to: 'mock_users#update'
14
14
  delete 'Users/:id', to: 'mock_users#destroy'
15
15
 
16
+ get 'Groups', to: 'mock_groups#index'
17
+ get 'Groups/:id', to: 'mock_groups#show'
18
+
16
19
  # For testing blocks passed to ActiveRecordBackedResourcesController#destroy
17
20
  #
18
21
  delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
@@ -1,6 +1,7 @@
1
1
  class CreateMockUsers < ActiveRecord::Migration[6.1]
2
2
  def change
3
- create_table :mock_users do |t|
3
+ create_table :mock_users, id: :uuid, primary_key: :primary_key do |t|
4
+ t.timestamps
4
5
 
5
6
  t.text :scim_uid
6
7
  t.text :username
@@ -9,7 +10,6 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
9
10
  t.text :work_email_address
10
11
  t.text :home_email_address
11
12
  t.text :work_phone_number
12
-
13
13
  end
14
14
  end
15
15
  end
@@ -1,8 +1,13 @@
1
1
  class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
2
2
  def change
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]
3
+ create_table :mock_groups_users, id: false do | t |
4
+ t.references :mock_group, foreign_key: true, type: :int8, index: true, null: false
5
+ t.references :mock_user, type: :uuid, index: true, null: false, primary_key: :primary_key
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
6
11
  end
7
12
  end
8
13
  end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2023_01_09_012729) do
13
+ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
14
14
 
15
15
  # These are extensions that must be enabled in order to support this database
16
16
  enable_extension "plpgsql"
@@ -24,12 +24,14 @@ ActiveRecord::Schema.define(version: 2023_01_09_012729) 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.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"
27
+ t.uuid "mock_user_id", null: false
28
+ t.index ["mock_group_id"], name: "index_mock_groups_users_on_mock_group_id"
29
+ t.index ["mock_user_id"], name: "index_mock_groups_users_on_mock_user_id"
30
30
  end
31
31
 
32
- create_table "mock_users", force: :cascade do |t|
32
+ create_table "mock_users", primary_key: "primary_key", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
33
+ t.datetime "created_at", precision: 6, null: false
34
+ t.datetime "updated_at", precision: 6, null: false
33
35
  t.text "scim_uid"
34
36
  t.text "username"
35
37
  t.text "first_name"
@@ -37,8 +39,8 @@ ActiveRecord::Schema.define(version: 2023_01_09_012729) do
37
39
  t.text "work_email_address"
38
40
  t.text "home_email_address"
39
41
  t.text "work_phone_number"
40
- t.datetime "created_at", precision: 6, null: false
41
- t.datetime "updated_at", precision: 6, null: false
42
42
  end
43
43
 
44
+ add_foreign_key "mock_groups_users", "mock_groups"
45
+ add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
44
46
  end
@@ -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(:id)).to eql([user_1.id])
408
+ expect(query.pluck(:primary_key)).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(:id)).to match_array([user_1.id, user_2.id])
414
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:id)).to eql([user_2.id])
420
+ expect(query.pluck(:primary_key)).to eql([user_2.primary_key])
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(:id)).to match_array([user_1.id, user_2.id])
428
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:id)).to match_array([user_1.id])
438
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key])
439
439
  end
440
440
 
441
441
  context 'when mapped to multiple columns' do
@@ -499,7 +499,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
499
499
  query = @instance.to_activerecord_query(MockUser.all)
500
500
 
501
501
  expect(query.count).to eql(1)
502
- expect(query.pluck(:id)).to match_array([user_2.id])
502
+ expect(query.pluck(:primary_key)).to match_array([user_2.primary_key])
503
503
  end
504
504
  end # "context 'simple AND' do"
505
505
 
@@ -520,7 +520,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
520
520
  query = @instance.to_activerecord_query(MockUser.all)
521
521
 
522
522
  expect(query.count).to eql(2)
523
- expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
523
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
524
524
  end
525
525
  end # "context 'simple OR' do"
526
526
 
@@ -546,7 +546,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
546
546
  query = @instance.to_activerecord_query(MockUser.all)
547
547
 
548
548
  expect(query.count).to eql(3)
549
- expect(query.pluck(:id)).to match_array([user_1.id, user_2.id, user_3.id])
549
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key, user_3.primary_key])
550
550
  end
551
551
  end # "context 'combined AND and OR' do"
552
552
 
@@ -159,41 +159,67 @@ RSpec.describe Scimitar::Resources::Mixin do
159
159
  # =========================================================================
160
160
 
161
161
  context '#to_scim' do
162
- it 'compiles instance attribute values into a SCIM representation' do
163
- instance = MockUser.new
164
- instance.id = 42
165
- instance.scim_uid = 'AA02984'
166
- instance.username = 'foo'
167
- instance.first_name = 'Foo'
168
- instance.last_name = 'Bar'
169
- instance.work_email_address = 'foo.bar@test.com'
170
- instance.home_email_address = nil
171
- instance.work_phone_number = '+642201234567'
172
-
173
- g1 = MockGroup.create!(display_name: 'Group 1')
174
- g2 = MockGroup.create!(display_name: 'Group 2')
175
- g3 = MockGroup.create!(display_name: 'Group 3')
176
-
177
- g1.mock_users << instance
178
- g3.mock_users << instance
179
-
180
- scim = instance.to_scim(location: 'https://test.com/mock_users/42')
181
- json = scim.to_json()
182
- hash = JSON.parse(json)
183
-
184
- expect(hash).to eql({
185
- 'userName' => 'foo',
186
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
187
- 'active' => true,
188
- 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
189
- 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
190
- 'id' => '42', # Note, String
191
- 'externalId' => 'AA02984',
192
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
193
- 'meta' => {'location'=>'https://test.com/mock_users/42', 'resourceType'=>'User'},
194
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
195
- })
196
- end
162
+ context 'with a UUID, renamed primary key column' do
163
+ it 'compiles instance attribute values into a SCIM representation' do
164
+ uuid = SecureRandom.uuid
165
+
166
+ instance = MockUser.new
167
+ instance.primary_key = uuid
168
+ instance.scim_uid = 'AA02984'
169
+ instance.username = 'foo'
170
+ instance.first_name = 'Foo'
171
+ instance.last_name = 'Bar'
172
+ instance.work_email_address = 'foo.bar@test.com'
173
+ instance.home_email_address = nil
174
+ instance.work_phone_number = '+642201234567'
175
+
176
+ g1 = MockGroup.create!(display_name: 'Group 1')
177
+ g2 = MockGroup.create!(display_name: 'Group 2')
178
+ g3 = MockGroup.create!(display_name: 'Group 3')
179
+
180
+ g1.mock_users << instance
181
+ g3.mock_users << instance
182
+
183
+ scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
184
+ json = scim.to_json()
185
+ hash = JSON.parse(json)
186
+
187
+ expect(hash).to eql({
188
+ 'userName' => 'foo',
189
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
190
+ 'active' => true,
191
+ 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
192
+ 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
193
+ 'id' => uuid,
194
+ 'externalId' => 'AA02984',
195
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
196
+ 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
197
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
198
+ })
199
+ end
200
+ end # "context 'with a UUID, renamed primary key column' do"
201
+
202
+ context 'with an integer, conventionally named primary key column' do
203
+ it 'compiles instance attribute values into a SCIM representation' do
204
+ instance = MockGroup.new
205
+ instance.id = 42
206
+ instance.scim_uid = 'GG02984'
207
+ instance.display_name = 'Some group'
208
+
209
+ scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
210
+ json = scim.to_json()
211
+ hash = JSON.parse(json)
212
+
213
+ expect(hash).to eql({
214
+ 'displayName' => 'Some group',
215
+ 'id' => '42', # Note, String
216
+ 'externalId' => 'GG02984',
217
+ 'members' => [],
218
+ 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
219
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
220
+ })
221
+ end
222
+ end # "context 'with an integer, conventionally named primary key column' do"
197
223
 
198
224
  context 'with optional timestamps' do
199
225
  context 'creation only' do
@@ -405,8 +431,8 @@ RSpec.describe Scimitar::Resources::Mixin do
405
431
  'displayName' => 'Foo Group',
406
432
  'members' => [
407
433
  {'type' => 'Group', 'value' => g1.id.to_s},
408
- {'type' => 'User', 'value' => u1.id.to_s},
409
- {'type' => 'User', 'value' => u3.id.to_s}
434
+ {'type' => 'User', 'value' => u1.primary_key.to_s},
435
+ {'type' => 'User', 'value' => u3.primary_key.to_s}
410
436
  ],
411
437
  'externalId' => 'GG01536',
412
438
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -453,8 +479,10 @@ RSpec.describe Scimitar::Resources::Mixin do
453
479
  end # "context 'using upper case' do"
454
480
 
455
481
  it 'clears things not present in input' do
482
+ uuid = SecureRandom.uuid
483
+
456
484
  instance = MockUser.new
457
- instance.id = 42
485
+ instance.primary_key = uuid
458
486
  instance.scim_uid = 'AA02984'
459
487
  instance.username = 'foo'
460
488
  instance.first_name = 'Foo'
@@ -465,7 +493,7 @@ RSpec.describe Scimitar::Resources::Mixin do
465
493
 
466
494
  instance.from_scim!(scim_hash: {})
467
495
 
468
- expect(instance.id ).to eql(42)
496
+ expect(instance.primary_key ).to eql(uuid)
469
497
  expect(instance.scim_uid ).to be_nil
470
498
  expect(instance.username ).to be_nil
471
499
  expect(instance.first_name ).to be_nil
@@ -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
- @u1 = MockUser.create(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)
11
- @u2 = MockUser.create(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)
12
- @u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
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
- it 'returns all items' do
36
- get '/Users', params: { format: :scim }
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
- expect(response.status).to eql(200)
39
- result = JSON.parse(response.body)
51
+ expect(response.status).to eql(200)
52
+ result = JSON.parse(response.body)
40
53
 
41
- expect(result['totalResults']).to eql(3)
42
- expect(result['Resources'].size).to eql(3)
54
+ expect(result['totalResults']).to eql(3)
55
+ expect(result['Resources'].size).to eql(3)
43
56
 
44
- ids = result['Resources'].map { |resource| resource['id'] }
45
- expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
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
- usernames = result['Resources'].map { |resource| resource['userName'] }
48
- expect(usernames).to match_array(['1', '2', '3'])
49
- end
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.id.to_s])
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.id.to_s])
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.id}\""
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.id.to_s])
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.id.to_s])
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.id.to_s])
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.id.to_s, @u2.id.to_s])
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.id.to_s, @u3.id.to_s])
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
- it 'shows an item' do
208
- expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
209
- get "/Users/#{@u2.id}", params: { format: :scim }
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
- expect(response.status).to eql(200)
212
- result = JSON.parse(response.body)
244
+ expect(response.status).to eql(200)
245
+ result = JSON.parse(response.body)
213
246
 
214
- expect(result['id']).to eql(@u2.id.to_s) # Note - ID was converted String; not Integer
215
- expect(result['userName']).to eql('2')
216
- expect(result['name']['familyName']).to eql('Bar')
217
- expect(result['meta']['resourceType']).to eql('User')
218
- end
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.id.to_s)
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.id.to_s)
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.id}", params: attributes.merge(format: :scim)
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.id.to_s)
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.id}", params: {
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.id}", params: payload.merge(format: :scim)
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.id.to_s)
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.id}", params: payload.merge(format: :scim)
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.id.to_s)
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.id}", params: payload.merge(format: :scim)
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.id.to_s)
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.id}", params: payload.merge(format: :scim)
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.id.to_s)
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.id}", params: {
652
+ patch "/Users/#{@u2.primary_key}", params: {
605
653
  format: :scim,
606
654
  Operations: [
607
655
  {
@@ -654,7 +702,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
654
702
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
655
703
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
656
704
  expect {
657
- delete "/Users/#{@u2.id}", params: { format: :scim }
705
+ delete "/Users/#{@u2.primary_key}", params: { format: :scim }
658
706
  }.to change { MockUser.count }.by(-1)
659
707
 
660
708
  expect(response.status).to eql(204)
@@ -666,7 +714,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
666
714
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
667
715
 
668
716
  expect {
669
- delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
717
+ delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
670
718
  }.to_not change { MockUser.count }
671
719
 
672
720
  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.3.3
4
+ version: 1.4.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-10 00:00:00.000000000 Z
12
+ date: 2023-01-26 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.1.6
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/spec_helper.rb
270
- - spec/models/scimitar/lists/query_parser_spec.rb
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
@@ -1,5 +0,0 @@
1
- class AddTimestampsToMockUser < ActiveRecord::Migration[6.1]
2
- def change
3
- add_timestamps :mock_users
4
- end
5
- end