scimitar 2.12.0 → 2.14.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/README.md +34 -5
- data/app/controllers/scimitar/application_controller.rb +30 -9
- data/app/models/scimitar/engine_configuration.rb +1 -0
- data/app/models/scimitar/resources/mixin.rb +18 -4
- data/lib/scimitar/version.rb +2 -2
- data/spec/apps/dummy/app/controllers/mock_batch_groups_controller.rb +13 -0
- data/spec/apps/dummy/app/models/mock_group_batch.rb +20 -0
- data/spec/apps/dummy/config/routes.rb +6 -0
- data/spec/controllers/scimitar/application_controller_spec.rb +74 -0
- data/spec/requests/active_record_backed_resources_controller_spec.rb +33 -0
- metadata +10 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5f63ca5b1572be0cb53a95a47280099713fd32be8f5650396a0db6a0c92550e
|
|
4
|
+
data.tar.gz: 2f1f2a7ec7a6877607875f6b86e7b9859ae9124f71d295541847940dd31c8b1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08626642f4b3e9dd29a6141c72bea784ab094ec197d9f3b9278957a59bfe230f4ef7ec84d52b1584127af647809351607bc01ef408d7269cc39dcf1f17180d49'
|
|
7
|
+
data.tar.gz: 53845ba684f54f8e2e90e696b769aadfe803809fa3004fa35f779794d6e04bfa034ee231847382591cdc5600cc7544bbad4c959e2031c0271e67e5632d431f6c
|
data/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Some aspects of configuration are handled via a `config/initializers/scimitar.rb
|
|
|
64
64
|
Rails.application.config.to_prepare do
|
|
65
65
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
66
66
|
# ...see subsections below for configuration options...
|
|
67
|
-
|
|
67
|
+
})
|
|
68
68
|
end
|
|
69
69
|
```
|
|
70
70
|
|
|
@@ -123,7 +123,7 @@ Here's an example where Warden is being used for authentication, with Warden sto
|
|
|
123
123
|
|
|
124
124
|
```ruby
|
|
125
125
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
126
|
-
|
|
126
|
+
custom_authenticator: Proc.new do
|
|
127
127
|
|
|
128
128
|
# In this example we catch the Warden 'throw' for failed authentication, as
|
|
129
129
|
# well as allowing Warden to successfully find an *authenticated* user, but
|
|
@@ -184,7 +184,36 @@ Note that Okta has some [curious documentation on its use of `POST` vs `PATCH` f
|
|
|
184
184
|
|
|
185
185
|
### Google Workspace note
|
|
186
186
|
|
|
187
|
-
Using SCIM with Google Workspace might only work for a subset of applications. Since web UIs for major service providers change very often, it doesn't make sense to provide extensive documentation here as it would get out of date quickly; you may have to figure out the setup as best you can using whatever current Google documentation exists for their system. There are [some notes which were relevant around mid-2025](https://github.com/pond/scimitar/issues/142#issuecomment-2699050541) (from when a
|
|
187
|
+
Using SCIM with Google Workspace might only work for a subset of applications. Since web UIs for major service providers change very often, it doesn't make sense to provide extensive documentation here as it would get out of date quickly; you may have to figure out the setup as best you can using whatever current Google documentation exists for their system. There are [some notes which were relevant around mid-2025](https://github.com/pond/scimitar/issues/142#issuecomment-2699050541) (from when a workaround/fix was incorporated into Scimitar to allow it to work with Google Workspace) which may help you get started.
|
|
188
|
+
|
|
189
|
+
### Request content type handling
|
|
190
|
+
|
|
191
|
+
The correct content type for SCIM is `application/scim+json`. Scimitar tolerates some variants of this, rewriting things internally so that the request continues to be processed by the rest of the gem (including ending up in subclass code you write) under this media type, with a Rails request format of `:scim`. Sometimes, callers into SCIM endpoints might use a content type that Scimitar rejects. If so, you can configure a custom request sanitizer Proc (with a "z", in keeping with Rails spelling of "sanitize"):
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
Rails.application.config.to_prepare do
|
|
195
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
|
196
|
+
|
|
197
|
+
custom_request_sanitizer: Proc.new do | request |
|
|
198
|
+
#
|
|
199
|
+
# Examine e.g. 'request.media_type' and evaluate to a Symbol:
|
|
200
|
+
#
|
|
201
|
+
# :success - set the standard content type and ensure the Rails request
|
|
202
|
+
# format is :scim; continue processing normally
|
|
203
|
+
#
|
|
204
|
+
# :preserve - retain the existing content type and Rails request format;
|
|
205
|
+
# continue processing normally
|
|
206
|
+
#
|
|
207
|
+
# :fail - the request format appears to be invalid; generate a 406
|
|
208
|
+
# ("Not Acceptable") response
|
|
209
|
+
#
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
})
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The Proc is passed the [`ActionDispatch::Request`](https://api.rubyonrails.org/classes/ActionDispatch/Request.html) instance for the current request. It **must** evaluable to a Symbol as shown above. Typically, `:preserve` is only for very special use cases where you understand that the request headers and/or format might not match what other parts of Scimitar expect, but have written appropriate custom code elsewhere to deal with that.
|
|
188
217
|
|
|
189
218
|
### Data models
|
|
190
219
|
|
|
@@ -255,8 +284,8 @@ class User < ActiveRecord::Base
|
|
|
255
284
|
],
|
|
256
285
|
|
|
257
286
|
# NB The 'groups' collection in a SCIM User resource is read-only, so
|
|
258
|
-
# we provide no ":find_with" key for looking up
|
|
259
|
-
# updates to the associated collection.
|
|
287
|
+
# we provide no ":find_with" or ":find_all_with" key for looking up
|
|
288
|
+
# records for writing updates to the associated collection.
|
|
260
289
|
#
|
|
261
290
|
groups: [
|
|
262
291
|
{
|
|
@@ -93,16 +93,37 @@ module Scimitar
|
|
|
93
93
|
#
|
|
94
94
|
def require_scim
|
|
95
95
|
scim_mime_type = Mime::Type.lookup_by_extension(:scim).to_s
|
|
96
|
+
failure_detail = "Only #{scim_mime_type} type is accepted."
|
|
97
|
+
|
|
98
|
+
if Scimitar.engine_configuration.custom_request_sanitizer.is_a?(Proc)
|
|
99
|
+
|
|
100
|
+
result = Scimitar.engine_configuration.custom_request_sanitizer.call(request)
|
|
101
|
+
case result
|
|
102
|
+
when :fail
|
|
103
|
+
handle_scim_error(ErrorResponse.new(status: 406, detail: failure_detail))
|
|
104
|
+
when :preserve
|
|
105
|
+
# Do nothing
|
|
106
|
+
else
|
|
107
|
+
request.format = :scim
|
|
108
|
+
request.headers['CONTENT_TYPE'] = scim_mime_type
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
else # "if Scimitar.engine_configuration.custom_request_sanitizer.present?"
|
|
112
|
+
|
|
113
|
+
if request.media_type.nil? || request.media_type.empty?
|
|
114
|
+
request.format = :scim
|
|
115
|
+
request.headers['CONTENT_TYPE'] = scim_mime_type
|
|
116
|
+
elsif request.media_type.downcase == scim_mime_type
|
|
117
|
+
request.format = :scim
|
|
118
|
+
elsif request.format == :scim
|
|
119
|
+
request.headers['CONTENT_TYPE'] = scim_mime_type
|
|
120
|
+
elsif request.media_type.downcase == 'application/json' && request.user_agent&.start_with?('Google') # https://github.com/pond/scimitar/issues/142
|
|
121
|
+
request.format = :scim
|
|
122
|
+
request.headers["CONTENT_TYPE"] = scim_mime_type
|
|
123
|
+
else
|
|
124
|
+
handle_scim_error(ErrorResponse.new(status: 406, detail: failure_detail))
|
|
125
|
+
end
|
|
96
126
|
|
|
97
|
-
if request.media_type.nil? || request.media_type.empty?
|
|
98
|
-
request.format = :scim
|
|
99
|
-
request.headers['CONTENT_TYPE'] = scim_mime_type
|
|
100
|
-
elsif request.media_type.downcase == scim_mime_type
|
|
101
|
-
request.format = :scim
|
|
102
|
-
elsif request.format == :scim
|
|
103
|
-
request.headers['CONTENT_TYPE'] = scim_mime_type
|
|
104
|
-
else
|
|
105
|
-
handle_scim_error(ErrorResponse.new(status: 406, detail: "Only #{scim_mime_type} type is accepted."))
|
|
106
127
|
end
|
|
107
128
|
end
|
|
108
129
|
|
|
@@ -145,7 +145,8 @@ module Scimitar
|
|
|
145
145
|
# display: :full_name # <-- i.e. Team.users[n].full_name
|
|
146
146
|
# },
|
|
147
147
|
# class: Team, # Optional; see below
|
|
148
|
-
# find_with:
|
|
148
|
+
# find_with: -> (scim_list_entry) {...}, # See below
|
|
149
|
+
# find_all_with: -> (scim_list_entries) {...} # Optional, See below
|
|
149
150
|
# }
|
|
150
151
|
# ],
|
|
151
152
|
# #...
|
|
@@ -165,6 +166,11 @@ module Scimitar
|
|
|
165
166
|
# Scimitar::EngineConfiguration::schema_list_from_attribute_mappings is
|
|
166
167
|
# defined; see documentation of that option for more information.
|
|
167
168
|
#
|
|
169
|
+
# To avoid N+1 queries when resolving many entries (e.g. Group members
|
|
170
|
+
# during PATCH), you can instead provide ":find_all_with" which is passed
|
|
171
|
+
# the entire Array of SCIM entries and should return an Array of resolved
|
|
172
|
+
# model instances. If both are provided, ":find_all_with" is preferred.
|
|
173
|
+
#
|
|
168
174
|
# Note that you can only use either:
|
|
169
175
|
#
|
|
170
176
|
# * One or more static maps where each matches some other piece of source
|
|
@@ -315,7 +321,7 @@ module Scimitar
|
|
|
315
321
|
enum.each do | static_or_dynamic_mapping |
|
|
316
322
|
if static_or_dynamic_mapping.key?(:match) # Static
|
|
317
323
|
extractor.call(static_or_dynamic_mapping[:using])
|
|
318
|
-
elsif static_or_dynamic_mapping.key?(:find_with) # Dynamic
|
|
324
|
+
elsif static_or_dynamic_mapping.key?(:find_with) || static_or_dynamic_mapping.key?(:find_all_with) # Dynamic
|
|
319
325
|
@scim_mutable_attributes << static_or_dynamic_mapping[:list]
|
|
320
326
|
end
|
|
321
327
|
end
|
|
@@ -839,9 +845,17 @@ module Scimitar
|
|
|
839
845
|
method = "#{mapped_array_entry[:list]}="
|
|
840
846
|
|
|
841
847
|
if (attribute&.mutability == 'readWrite' || attribute&.mutability == 'writeOnly') && self.respond_to?(method)
|
|
842
|
-
|
|
848
|
+
find_all_with_proc = mapped_array_entry[:find_all_with]
|
|
849
|
+
find_with_proc = mapped_array_entry[:find_with]
|
|
850
|
+
|
|
851
|
+
if find_all_with_proc.respond_to?(:call)
|
|
852
|
+
scim_entries = (scim_hash_or_leaf_value || [])
|
|
853
|
+
mapped_list = find_all_with_proc.call(scim_entries) || []
|
|
843
854
|
|
|
844
|
-
|
|
855
|
+
mapped_list.compact!
|
|
856
|
+
|
|
857
|
+
self.public_send(method, mapped_list)
|
|
858
|
+
elsif find_with_proc.respond_to?(:call)
|
|
845
859
|
mapped_list = (scim_hash_or_leaf_value || []).map do | source_list_entry |
|
|
846
860
|
find_with_proc.call(source_list_entry)
|
|
847
861
|
end
|
data/lib/scimitar/version.rb
CHANGED
|
@@ -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.
|
|
6
|
+
VERSION = '2.14.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 = '2025-
|
|
11
|
+
DATE = '2025-11-14'
|
|
12
12
|
|
|
13
13
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class MockGroupBatch < MockGroup
|
|
2
|
+
def self.scim_attributes_map
|
|
3
|
+
{
|
|
4
|
+
id: :id,
|
|
5
|
+
externalId: :scim_uid,
|
|
6
|
+
displayName: :display_name,
|
|
7
|
+
members: [
|
|
8
|
+
{
|
|
9
|
+
list: :scim_users_and_groups,
|
|
10
|
+
using: { value: :id },
|
|
11
|
+
# Minimal mock: assume user-only entries (type omitted => User)
|
|
12
|
+
find_all_with: -> (entries) do
|
|
13
|
+
ids = entries.map { |e| e['value'] }
|
|
14
|
+
MockUser.where(primary_key: ids).to_a
|
|
15
|
+
end
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -17,6 +17,12 @@ Rails.application.routes.draw do
|
|
|
17
17
|
get 'Groups/:id', to: 'mock_groups#show'
|
|
18
18
|
patch 'Groups/:id', to: 'mock_groups#update'
|
|
19
19
|
|
|
20
|
+
# Batch lookup variant for testing the mixin 'find_all_with' option.
|
|
21
|
+
#
|
|
22
|
+
get 'BatchGroups', to: 'mock_batch_groups#index'
|
|
23
|
+
get 'BatchGroups/:id', to: 'mock_batch_groups#show'
|
|
24
|
+
patch 'BatchGroups/:id', to: 'mock_batch_groups#update'
|
|
25
|
+
|
|
20
26
|
# For testing blocks passed to ActiveRecordBackedResourcesController#create,
|
|
21
27
|
# #update, #replace and #destroy.
|
|
22
28
|
#
|
|
@@ -416,6 +416,80 @@ RSpec.describe Scimitar::ApplicationController do
|
|
|
416
416
|
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
|
417
417
|
end
|
|
418
418
|
end
|
|
419
|
+
|
|
420
|
+
context 'and with Google SCIM calls' do
|
|
421
|
+
it 'reaches the controller action if the expected agent is making the request' do
|
|
422
|
+
request.headers['Content-Type'] = 'application/json'
|
|
423
|
+
request.headers['User-Agent' ] = 'Google-Auto-Provisioning'
|
|
424
|
+
get :index
|
|
425
|
+
|
|
426
|
+
expect(@exception).to be_a(RuntimeError)
|
|
427
|
+
expect(@exception.message).to eql('Bang')
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
it 'is invoked early for unrecognised agents' do
|
|
431
|
+
request.headers['Content-Type'] = 'application/json'
|
|
432
|
+
get :index
|
|
433
|
+
|
|
434
|
+
expect(@exception).to be_a(Scimitar::ErrorResponse)
|
|
435
|
+
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
|
436
|
+
end
|
|
437
|
+
end # "context 'and with Google SCIM calls' do"
|
|
438
|
+
|
|
439
|
+
context 'and with a custom request sanitizer' do
|
|
440
|
+
around :each do | example |
|
|
441
|
+
original_configuration = Scimitar.engine_configuration.custom_request_sanitizer
|
|
442
|
+
Scimitar.engine_configuration.custom_request_sanitizer = Proc.new do | request |
|
|
443
|
+
case request.media_type
|
|
444
|
+
when 'application/json+success'
|
|
445
|
+
:success
|
|
446
|
+
when 'application/json+preserve'
|
|
447
|
+
:preserve
|
|
448
|
+
else
|
|
449
|
+
:fail
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
example.run()
|
|
453
|
+
ensure
|
|
454
|
+
Scimitar.engine_configuration.custom_request_sanitizer = original_configuration
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
context 'returning "success"' do
|
|
458
|
+
it 'reaches the controller action with cleaned up request data' do
|
|
459
|
+
request.headers['Content-Type'] = 'application/json+success'
|
|
460
|
+
get :index
|
|
461
|
+
|
|
462
|
+
expect(@exception).to be_a(RuntimeError)
|
|
463
|
+
expect(@exception.message).to eql('Bang')
|
|
464
|
+
|
|
465
|
+
expect(request.format == :scim).to eql(true)
|
|
466
|
+
expect(request.headers['CONTENT_TYPE']).to eql('application/scim+json')
|
|
467
|
+
end
|
|
468
|
+
end # "context 'returning "success"' do"
|
|
469
|
+
|
|
470
|
+
context 'returning "preserve"' do
|
|
471
|
+
it 'reaches the controller action with unmodified request data' do
|
|
472
|
+
request.headers['Content-Type'] = 'application/json+preserve'
|
|
473
|
+
get :index
|
|
474
|
+
|
|
475
|
+
expect(@exception).to be_a(RuntimeError)
|
|
476
|
+
expect(@exception.message).to eql('Bang')
|
|
477
|
+
|
|
478
|
+
expect(request.format == :html).to eql(true)
|
|
479
|
+
expect(request.headers['CONTENT_TYPE']).to eql('application/json+preserve')
|
|
480
|
+
end
|
|
481
|
+
end # "context 'returning "keep"' do"
|
|
482
|
+
|
|
483
|
+
context 'returning "fail"' do
|
|
484
|
+
it 'is invoked' do
|
|
485
|
+
request.headers['Content-Type'] = 'application/json+fail'
|
|
486
|
+
get :index
|
|
487
|
+
|
|
488
|
+
expect(@exception).to be_a(Scimitar::ErrorResponse)
|
|
489
|
+
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
|
490
|
+
end
|
|
491
|
+
end # "context 'returning "fail"' do"
|
|
492
|
+
end # "context 'and with a custom request sanitizer' do"
|
|
419
493
|
end # "context 'exception reporter' do"
|
|
420
494
|
end # "context 'error handling' do"
|
|
421
495
|
end
|
|
@@ -1137,6 +1137,39 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
|
1137
1137
|
expect(@u2.password).to eql('correcthorsebatterystaple')
|
|
1138
1138
|
end
|
|
1139
1139
|
|
|
1140
|
+
context "when updating group members using :find_all_with" do
|
|
1141
|
+
it "uses :find_all_with to batch-resolve users and updates associations" do
|
|
1142
|
+
payload = {
|
|
1143
|
+
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:PatchOp' ],
|
|
1144
|
+
Operations: [
|
|
1145
|
+
{
|
|
1146
|
+
op: 'add',
|
|
1147
|
+
path: 'members',
|
|
1148
|
+
value: [
|
|
1149
|
+
{ 'value' => @u1.primary_key },
|
|
1150
|
+
{ 'value' => @u2.primary_key }
|
|
1151
|
+
]
|
|
1152
|
+
}
|
|
1153
|
+
]
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
payload = spec_helper_hupcase(payload) if force_upper_case
|
|
1157
|
+
|
|
1158
|
+
patch "/BatchGroups/#{@g1.id}", params: payload.merge({ format: :scim })
|
|
1159
|
+
|
|
1160
|
+
expect(response.status).to eql(200)
|
|
1161
|
+
|
|
1162
|
+
# Verify membership updated
|
|
1163
|
+
get "/BatchGroups/#{@g1.id}", params: { format: :scim }
|
|
1164
|
+
expect(response.status).to eql(200)
|
|
1165
|
+
result = JSON.parse(response.body)
|
|
1166
|
+
|
|
1167
|
+
values = result.fetch('members', []).map { |m| m['value'] }
|
|
1168
|
+
expect(values).to include(@u1.primary_key.to_s)
|
|
1169
|
+
expect(values).to include(@u2.primary_key.to_s)
|
|
1170
|
+
end
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1140
1173
|
context 'which clears attributes' do
|
|
1141
1174
|
before :each do
|
|
1142
1175
|
@u2.update!(work_email_address: 'work_2@test.com')
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scimitar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- RIPA Global
|
|
8
8
|
- Andrew David Hodgkinson
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '13.
|
|
47
|
+
version: '13.3'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '13.
|
|
54
|
+
version: '13.3'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: pg
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '6.
|
|
89
|
+
version: '6.15'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '6.
|
|
96
|
+
version: '6.15'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: warden
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -230,9 +230,11 @@ files:
|
|
|
230
230
|
- spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
|
|
231
231
|
- spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
|
|
232
232
|
- spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb
|
|
233
|
+
- spec/apps/dummy/app/controllers/mock_batch_groups_controller.rb
|
|
233
234
|
- spec/apps/dummy/app/controllers/mock_groups_controller.rb
|
|
234
235
|
- spec/apps/dummy/app/controllers/mock_users_controller.rb
|
|
235
236
|
- spec/apps/dummy/app/models/mock_group.rb
|
|
237
|
+
- spec/apps/dummy/app/models/mock_group_batch.rb
|
|
236
238
|
- spec/apps/dummy/app/models/mock_user.rb
|
|
237
239
|
- spec/apps/dummy/config/application.rb
|
|
238
240
|
- spec/apps/dummy/config/boot.rb
|
|
@@ -304,9 +306,11 @@ test_files:
|
|
|
304
306
|
- spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
|
|
305
307
|
- spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
|
|
306
308
|
- spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb
|
|
309
|
+
- spec/apps/dummy/app/controllers/mock_batch_groups_controller.rb
|
|
307
310
|
- spec/apps/dummy/app/controllers/mock_groups_controller.rb
|
|
308
311
|
- spec/apps/dummy/app/controllers/mock_users_controller.rb
|
|
309
312
|
- spec/apps/dummy/app/models/mock_group.rb
|
|
313
|
+
- spec/apps/dummy/app/models/mock_group_batch.rb
|
|
310
314
|
- spec/apps/dummy/app/models/mock_user.rb
|
|
311
315
|
- spec/apps/dummy/config/application.rb
|
|
312
316
|
- spec/apps/dummy/config/boot.rb
|