scimitar 2.2.0 → 2.3.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: ce356c067dc4636a1540eec905c21730c6d37d7831725ed5fd914b0a62e7f1e7
4
- data.tar.gz: 979360b39861dc14e5d202ab580774e6164f9818b0358a8bcc47e06c6a6462c3
3
+ metadata.gz: 3904e389b3b00b4c49df6d9267cc0b5413613ed56b544ee888dd4b3bfa9d7489
4
+ data.tar.gz: 93b14676bda6d86d4add6c0bcbf8ae6415ff49177b384f2b75571a304485d87f
5
5
  SHA512:
6
- metadata.gz: 78eeeab146e8a18f265643bc99a32a461a7972b2971309421f4cbfa1751fabe0ad99a84037f8f2000ef5da90ae7282bf16c66fa34e26ed83e4ae2510248df651
7
- data.tar.gz: 15af818f707e5a7db410abd57fe342258c016fdf6fef79af12e384481f7730bdea7a22983e3fd662317e73437fdb0c491a651639479539a8029eebe3dd8d3ab6
6
+ metadata.gz: f1fad29d6ba090ad00b55bdf59f1050aacd1858d4e511e726d3fa501a37389e57777cd2cb2253c20972106e567a3f4b5e07ecd53a17723dcab2517f0c8ab99cf
7
+ data.tar.gz: 30a1c3ad4c6653b347cf00228cad99b88a3dc4a2da45fe311b61d0a0f2d08db723fabf2041e3d72f1d1cea28d633c540322394841723bdd8db7d805e49819eb4
@@ -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 = '2.2.0'
6
+ VERSION = '2.3.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-13'
11
+ DATE = '2023-01-17'
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
@@ -1,11 +1,13 @@
1
1
  class MockUser < ActiveRecord::Base
2
2
 
3
+ self.primary_key = :primary_key
4
+
3
5
  # ===========================================================================
4
6
  # TEST ATTRIBUTES - see db/migrate/20210304014602_create_mock_users.rb etc.
5
7
  # ===========================================================================
6
8
 
7
9
  READWRITE_ATTRS = %w{
8
- id
10
+ primary_key
9
11
  scim_uid
10
12
  username
11
13
  first_name
@@ -38,7 +40,7 @@ class MockUser < ActiveRecord::Base
38
40
 
39
41
  def self.scim_attributes_map
40
42
  return {
41
- id: :id,
43
+ id: :primary_key,
42
44
  externalId: :scim_uid,
43
45
  userName: :username,
44
46
  name: {
@@ -92,7 +94,7 @@ class MockUser < ActiveRecord::Base
92
94
 
93
95
  def self.scim_queryable_attributes
94
96
  return {
95
- 'id' => { column: :id },
97
+ 'id' => { column: :primary_key },
96
98
  'externalId' => { column: :scim_uid },
97
99
  'meta.lastModified' => { column: :updated_at },
98
100
  'name.givenName' => { column: :first_name },
@@ -6,12 +6,15 @@
6
6
  Rails.application.routes.draw do
7
7
  mount Scimitar::Engine, at: '/'
8
8
 
9
- get 'Users', to: 'mock_users#index'
10
- get 'Users/:id', to: 'mock_users#show'
11
- post 'Users', to: 'mock_users#create'
12
- put 'Users/:id', to: 'mock_users#replace'
13
- patch 'Users/:id', to: 'mock_users#update'
14
- delete 'Users/:id', to: 'mock_users#destroy'
9
+ get 'Users', to: 'mock_users#index'
10
+ get 'Users/:id', to: 'mock_users#show'
11
+ post 'Users', to: 'mock_users#create'
12
+ put 'Users/:id', to: 'mock_users#replace'
13
+ patch 'Users/:id', to: 'mock_users#update'
14
+ delete 'Users/:id', to: 'mock_users#destroy'
15
+
16
+ get 'Groups', to: 'mock_groups#index'
17
+ get 'Groups/:id', to: 'mock_groups#show'
15
18
 
16
19
  # For testing blocks passed to ActiveRecordBackedResourcesController#destroy
17
20
  #
@@ -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[7.0].define(version: 2023_01_09_012729) do
13
+ ActiveRecord::Schema[7.0].define(version: 2021_03_08_044214) do
14
14
  # These are extensions that must be enabled in order to support this database
15
15
  enable_extension "plpgsql"
16
16
 
@@ -23,12 +23,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_09_012729) do
23
23
 
24
24
  create_table "mock_groups_users", id: false, force: :cascade do |t|
25
25
  t.bigint "mock_group_id", null: false
26
- t.bigint "mock_user_id", null: false
27
- t.index ["mock_group_id", "mock_user_id"], name: "index_mock_groups_users_on_mock_group_id_and_mock_user_id"
28
- t.index ["mock_user_id", "mock_group_id"], name: "index_mock_groups_users_on_mock_user_id_and_mock_group_id"
26
+ t.uuid "mock_user_id", null: false
27
+ t.index ["mock_group_id"], name: "index_mock_groups_users_on_mock_group_id"
28
+ t.index ["mock_user_id"], name: "index_mock_groups_users_on_mock_user_id"
29
29
  end
30
30
 
31
- create_table "mock_users", force: :cascade do |t|
31
+ create_table "mock_users", primary_key: "primary_key", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
32
+ t.datetime "created_at", null: false
33
+ t.datetime "updated_at", null: false
32
34
  t.text "scim_uid"
33
35
  t.text "username"
34
36
  t.text "first_name"
@@ -36,8 +38,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_09_012729) do
36
38
  t.text "work_email_address"
37
39
  t.text "home_email_address"
38
40
  t.text "work_phone_number"
39
- t.datetime "created_at", null: false
40
- t.datetime "updated_at", null: false
41
41
  end
42
42
 
43
+ add_foreign_key "mock_groups_users", "mock_groups"
44
+ add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
43
45
  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.primary_key])
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
@@ -5,11 +5,21 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
5
5
  before :each do
6
6
  allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
7
7
 
8
+ # If a sort order is unspecified, the controller defaults to ID ascending.
9
+ # With UUID based IDs, testing life is made easier by ensuring that the
10
+ # creation order matches an ascending UUID sort order (which is what would
11
+ # happen if we were using integer primary keys).
12
+ #
8
13
  lmt = Time.parse("2023-01-09 14:25:00 +1300")
14
+ ids = 3.times.map { SecureRandom.uuid }.sort()
9
15
 
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)
16
+ @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)
17
+ @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)
18
+ @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)
19
+
20
+ @g1 = MockGroup.create!(display_name: 'Group 1')
21
+ @g2 = MockGroup.create!(display_name: 'Group 2')
22
+ @g3 = MockGroup.create!(display_name: 'Group 3')
13
23
  end
14
24
 
15
25
  # ===========================================================================
@@ -32,21 +42,41 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
32
42
  end # "context 'with no items' do"
33
43
 
34
44
  context 'with items' do
35
- it 'returns all items' do
36
- get '/Users', params: { format: :scim }
45
+ context 'with a UUID, renamed primary key column' do
46
+ it 'returns all items' do
47
+ get '/Users', params: { format: :scim }
37
48
 
38
- expect(response.status).to eql(200)
39
- result = JSON.parse(response.body)
49
+ expect(response.status).to eql(200)
50
+ result = JSON.parse(response.body)
40
51
 
41
- expect(result['totalResults']).to eql(3)
42
- expect(result['Resources'].size).to eql(3)
52
+ expect(result['totalResults']).to eql(3)
53
+ expect(result['Resources'].size).to eql(3)
43
54
 
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])
55
+ ids = result['Resources'].map { |resource| resource['id'] }
56
+ expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
46
57
 
47
- usernames = result['Resources'].map { |resource| resource['userName'] }
48
- expect(usernames).to match_array(['1', '2', '3'])
49
- end
58
+ usernames = result['Resources'].map { |resource| resource['userName'] }
59
+ expect(usernames).to match_array(['1', '2', '3'])
60
+ end
61
+ end # "context 'with a UUID, renamed primary key column' do"
62
+
63
+ context 'with an integer, conventionally named primary key column' do
64
+ it 'returns all items' do
65
+ get '/Groups', params: { format: :scim }
66
+
67
+ expect(response.status).to eql(200)
68
+ result = JSON.parse(response.body)
69
+
70
+ expect(result['totalResults']).to eql(3)
71
+ expect(result['Resources'].size).to eql(3)
72
+
73
+ ids = result['Resources'].map { |resource| resource['id'] }
74
+ expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
75
+
76
+ usernames = result['Resources'].map { |resource| resource['displayName'] }
77
+ expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
78
+ end
79
+ end # "context 'with an integer, conventionally named primary key column' do"
50
80
 
51
81
  it 'applies a filter, with case-insensitive value comparison' do
52
82
  get '/Users', params: {
@@ -61,7 +91,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
61
91
  expect(result['Resources'].size).to eql(1)
62
92
 
63
93
  ids = result['Resources'].map { |resource| resource['id'] }
64
- expect(ids).to match_array([@u2.id.to_s])
94
+ expect(ids).to match_array([@u2.primary_key.to_s])
65
95
 
66
96
  usernames = result['Resources'].map { |resource| resource['userName'] }
67
97
  expect(usernames).to match_array(['2'])
@@ -80,7 +110,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
80
110
  expect(result['Resources'].size).to eql(1)
81
111
 
82
112
  ids = result['Resources'].map { |resource| resource['id'] }
83
- expect(ids).to match_array([@u2.id.to_s])
113
+ expect(ids).to match_array([@u2.primary_key.to_s])
84
114
 
85
115
  usernames = result['Resources'].map { |resource| resource['userName'] }
86
116
  expect(usernames).to match_array(['2'])
@@ -93,7 +123,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
93
123
  it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
94
124
  get '/Users', params: {
95
125
  format: :scim,
96
- filter: "id eq \"#{@u3.id}\""
126
+ filter: "id eq \"#{@u3.primary_key}\""
97
127
  }
98
128
 
99
129
  expect(response.status).to eql(200)
@@ -103,7 +133,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
103
133
  expect(result['Resources'].size).to eql(1)
104
134
 
105
135
  ids = result['Resources'].map { |resource| resource['id'] }
106
- expect(ids).to match_array([@u3.id.to_s])
136
+ expect(ids).to match_array([@u3.primary_key.to_s])
107
137
 
108
138
  usernames = result['Resources'].map { |resource| resource['userName'] }
109
139
  expect(usernames).to match_array(['3'])
@@ -122,7 +152,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
122
152
  expect(result['Resources'].size).to eql(1)
123
153
 
124
154
  ids = result['Resources'].map { |resource| resource['id'] }
125
- expect(ids).to match_array([@u2.id.to_s])
155
+ expect(ids).to match_array([@u2.primary_key.to_s])
126
156
 
127
157
  usernames = result['Resources'].map { |resource| resource['userName'] }
128
158
  expect(usernames).to match_array(['2'])
@@ -141,7 +171,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
141
171
  expect(result['Resources'].size).to eql(1)
142
172
 
143
173
  ids = result['Resources'].map { |resource| resource['id'] }
144
- expect(ids).to match_array([@u3.id.to_s])
174
+ expect(ids).to match_array([@u3.primary_key.to_s])
145
175
 
146
176
  usernames = result['Resources'].map { |resource| resource['userName'] }
147
177
  expect(usernames).to match_array(['3'])
@@ -161,7 +191,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
161
191
  expect(result['Resources'].size).to eql(2)
162
192
 
163
193
  ids = result['Resources'].map { |resource| resource['id'] }
164
- expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
194
+ expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s])
165
195
 
166
196
  usernames = result['Resources'].map { |resource| resource['userName'] }
167
197
  expect(usernames).to match_array(['1', '2'])
@@ -180,7 +210,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
180
210
  expect(result['Resources'].size).to eql(2)
181
211
 
182
212
  ids = result['Resources'].map { |resource| resource['id'] }
183
- expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
213
+ expect(ids).to match_array([@u2.primary_key.to_s, @u3.primary_key.to_s])
184
214
 
185
215
  usernames = result['Resources'].map { |resource| resource['userName'] }
186
216
  expect(usernames).to match_array(['2', '3'])
@@ -204,18 +234,34 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
204
234
  # ===========================================================================
205
235
 
206
236
  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 }
237
+ context 'with a UUID, renamed primary key column' do
238
+ it 'shows an item' do
239
+ expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
240
+ get "/Users/#{@u2.primary_key}", params: { format: :scim }
210
241
 
211
- expect(response.status).to eql(200)
212
- result = JSON.parse(response.body)
242
+ expect(response.status).to eql(200)
243
+ result = JSON.parse(response.body)
213
244
 
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
245
+ expect(result['id']).to eql(@u2.primary_key.to_s)
246
+ expect(result['userName']).to eql('2')
247
+ expect(result['name']['familyName']).to eql('Bar')
248
+ expect(result['meta']['resourceType']).to eql('User')
249
+ end
250
+ end # "context 'with a UUID, renamed primary key column' do"
251
+
252
+ context 'with an integer, conventionally named primary key column' do
253
+ it 'shows an item' do
254
+ expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
255
+ get "/Groups/#{@g2.id}", params: { format: :scim }
256
+
257
+ expect(response.status).to eql(200)
258
+ result = JSON.parse(response.body)
259
+
260
+ expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
261
+ expect(result['displayName']).to eql('Group 2')
262
+ expect(result['meta']['resourceType']).to eql('Group')
263
+ end
264
+ end # "context 'with an integer, conventionally named primary key column' do"
219
265
 
220
266
  it 'renders 404' do
221
267
  get '/Users/xyz', params: { format: :scim }
@@ -234,7 +280,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
234
280
  it 'with minimal parameters' do
235
281
  mock_before = MockUser.all.to_a
236
282
 
237
- attributes = { userName: '4' } # Minimum required by schema
283
+ attributes = { userName: '4' } # Minimum required by schema
238
284
  attributes = spec_helper_hupcase(attributes) if force_upper_case
239
285
 
240
286
  expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
@@ -248,7 +294,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
248
294
  expect(response.status).to eql(201)
249
295
  result = JSON.parse(response.body)
250
296
 
251
- expect(result['id']).to eql(new_mock.id.to_s)
297
+ expect(result['id']).to eql(new_mock.primary_key.to_s)
252
298
  expect(result['meta']['resourceType']).to eql('User')
253
299
  expect(new_mock.username).to eql('4')
254
300
  end
@@ -363,13 +409,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
363
409
 
364
410
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
365
411
  expect {
366
- put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
412
+ put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
367
413
  }.to_not change { MockUser.count }
368
414
 
369
415
  expect(response.status).to eql(200)
370
416
  result = JSON.parse(response.body)
371
417
 
372
- expect(result['id']).to eql(@u2.id.to_s)
418
+ expect(result['id']).to eql(@u2.primary_key.to_s)
373
419
  expect(result['meta']['resourceType']).to eql('User')
374
420
 
375
421
  @u2.reload
@@ -391,7 +437,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
391
437
 
392
438
  it 'notes schema validation failures' do
393
439
  expect {
394
- put "/Users/#{@u2.id}", params: {
440
+ put "/Users/#{@u2.primary_key}", params: {
395
441
  format: :scim
396
442
  # userName parameter is required by schema, but missing
397
443
  }
@@ -470,13 +516,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
470
516
 
471
517
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
472
518
  expect {
473
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
519
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
474
520
  }.to_not change { MockUser.count }
475
521
 
476
522
  expect(response.status).to eql(200)
477
523
  result = JSON.parse(response.body)
478
524
 
479
- expect(result['id']).to eql(@u2.id.to_s)
525
+ expect(result['id']).to eql(@u2.primary_key.to_s)
480
526
  expect(result['meta']['resourceType']).to eql('User')
481
527
 
482
528
  @u2.reload
@@ -507,13 +553,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
507
553
 
508
554
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
509
555
  expect {
510
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
556
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
511
557
  }.to_not change { MockUser.count }
512
558
 
513
559
  expect(response.status).to eql(200)
514
560
  result = JSON.parse(response.body)
515
561
 
516
- expect(result['id']).to eql(@u2.id.to_s)
562
+ expect(result['id']).to eql(@u2.primary_key.to_s)
517
563
  expect(result['meta']['resourceType']).to eql('User')
518
564
 
519
565
  @u2.reload
@@ -539,13 +585,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
539
585
 
540
586
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
541
587
  expect {
542
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
588
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
543
589
  }.to_not change { MockUser.count }
544
590
 
545
591
  expect(response.status).to eql(200)
546
592
  result = JSON.parse(response.body)
547
593
 
548
- expect(result['id']).to eql(@u2.id.to_s)
594
+ expect(result['id']).to eql(@u2.primary_key.to_s)
549
595
  expect(result['meta']['resourceType']).to eql('User')
550
596
 
551
597
  @u2.reload
@@ -571,13 +617,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
571
617
 
572
618
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
573
619
  expect {
574
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
620
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
575
621
  }.to_not change { MockUser.count }
576
622
 
577
623
  expect(response.status).to eql(200)
578
624
  result = JSON.parse(response.body)
579
625
 
580
- expect(result['id']).to eql(@u2.id.to_s)
626
+ expect(result['id']).to eql(@u2.primary_key.to_s)
581
627
  expect(result['meta']['resourceType']).to eql('User')
582
628
 
583
629
  @u2.reload
@@ -601,7 +647,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
601
647
 
602
648
  it 'notes Rails validation failures' do
603
649
  expect {
604
- patch "/Users/#{@u2.id}", params: {
650
+ patch "/Users/#{@u2.primary_key}", params: {
605
651
  format: :scim,
606
652
  Operations: [
607
653
  {
@@ -654,7 +700,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
654
700
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
655
701
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
656
702
  expect {
657
- delete "/Users/#{@u2.id}", params: { format: :scim }
703
+ delete "/Users/#{@u2.primary_key}", params: { format: :scim }
658
704
  }.to change { MockUser.count }.by(-1)
659
705
 
660
706
  expect(response.status).to eql(204)
@@ -666,7 +712,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
666
712
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
667
713
 
668
714
  expect {
669
- delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
715
+ delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
670
716
  }.to_not change { MockUser.count }
671
717
 
672
718
  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: 2.2.0
4
+ version: 2.3.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-13 00:00:00.000000000 Z
12
+ date: 2023-01-17 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,7 +260,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
260
  - !ruby/object:Gem::Version
262
261
  version: '0'
263
262
  requirements: []
264
- rubygems_version: 3.4.1
263
+ rubygems_version: 3.4.4
265
264
  signing_key:
266
265
  specification_version: 4
267
266
  summary: SCIM v2 for Rails
@@ -283,7 +282,6 @@ test_files:
283
282
  - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb
284
283
  - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
285
284
  - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
286
- - spec/apps/dummy/db/migrate/20230109012729_add_timestamps_to_mock_user.rb
287
285
  - spec/apps/dummy/db/schema.rb
288
286
  - spec/controllers/scimitar/application_controller_spec.rb
289
287
  - spec/controllers/scimitar/resource_types_controller_spec.rb
@@ -1,5 +0,0 @@
1
- class AddTimestampsToMockUser < ActiveRecord::Migration[7.0]
2
- def change
3
- add_timestamps :mock_users
4
- end
5
- end