scim_engine 2.1.1

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +25 -0
  4. data/app/controllers/scim_engine/application_controller.rb +36 -0
  5. data/app/controllers/scim_engine/resource_types_controller.rb +22 -0
  6. data/app/controllers/scim_engine/resources_controller.rb +71 -0
  7. data/app/controllers/scim_engine/schemas_controller.rb +16 -0
  8. data/app/controllers/scim_engine/service_provider_configurations_controller.rb +8 -0
  9. data/app/models/scim_engine/authentication_error.rb +9 -0
  10. data/app/models/scim_engine/authentication_scheme.rb +12 -0
  11. data/app/models/scim_engine/bulk.rb +5 -0
  12. data/app/models/scim_engine/complex_types/base.rb +37 -0
  13. data/app/models/scim_engine/complex_types/email.rb +14 -0
  14. data/app/models/scim_engine/complex_types/name.rb +9 -0
  15. data/app/models/scim_engine/complex_types/reference.rb +9 -0
  16. data/app/models/scim_engine/error_response.rb +13 -0
  17. data/app/models/scim_engine/errors.rb +14 -0
  18. data/app/models/scim_engine/filter.rb +5 -0
  19. data/app/models/scim_engine/meta.rb +7 -0
  20. data/app/models/scim_engine/not_found_error.rb +10 -0
  21. data/app/models/scim_engine/resource_invalid_error.rb +9 -0
  22. data/app/models/scim_engine/resource_type.rb +29 -0
  23. data/app/models/scim_engine/resources/base.rb +139 -0
  24. data/app/models/scim_engine/resources/group.rb +13 -0
  25. data/app/models/scim_engine/resources/user.rb +13 -0
  26. data/app/models/scim_engine/schema/attribute.rb +91 -0
  27. data/app/models/scim_engine/schema/base.rb +35 -0
  28. data/app/models/scim_engine/schema/derived_attributes.rb +24 -0
  29. data/app/models/scim_engine/schema/email.rb +15 -0
  30. data/app/models/scim_engine/schema/group.rb +27 -0
  31. data/app/models/scim_engine/schema/name.rb +17 -0
  32. data/app/models/scim_engine/schema/reference.rb +14 -0
  33. data/app/models/scim_engine/schema/user.rb +30 -0
  34. data/app/models/scim_engine/service_provider_configuration.rb +31 -0
  35. data/app/models/scim_engine/supportable.rb +10 -0
  36. data/app/views/layouts/scim_engine/application.html.erb +14 -0
  37. data/config/routes.rb +6 -0
  38. data/lib/scim_engine.rb +13 -0
  39. data/lib/scim_engine/engine.rb +51 -0
  40. data/lib/scim_engine/version.rb +3 -0
  41. data/lib/tasks/scim_engine_tasks.rake +4 -0
  42. data/spec/controllers/scim_engine/application_controller_spec.rb +104 -0
  43. data/spec/controllers/scim_engine/resource_types_controller_spec.rb +78 -0
  44. data/spec/controllers/scim_engine/resources_controller_spec.rb +132 -0
  45. data/spec/controllers/scim_engine/schemas_controller_spec.rb +66 -0
  46. data/spec/controllers/scim_engine/service_provider_configurations_controller_spec.rb +21 -0
  47. data/spec/dummy/README.rdoc +28 -0
  48. data/spec/dummy/Rakefile +6 -0
  49. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  50. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  51. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  52. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/bin/bundle +3 -0
  55. data/spec/dummy/bin/rails +4 -0
  56. data/spec/dummy/bin/rake +4 -0
  57. data/spec/dummy/bin/setup +29 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +16 -0
  60. data/spec/dummy/config/boot.rb +5 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +41 -0
  63. data/spec/dummy/config/environments/production.rb +79 -0
  64. data/spec/dummy/config/environments/test.rb +42 -0
  65. data/spec/dummy/config/initializers/assets.rb +11 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/spec/dummy/config/initializers/inflections.rb +16 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +23 -0
  75. data/spec/dummy/config/routes.rb +4 -0
  76. data/spec/dummy/config/secrets.yml +22 -0
  77. data/spec/dummy/log/test.log +175 -0
  78. data/spec/dummy/public/404.html +67 -0
  79. data/spec/dummy/public/422.html +67 -0
  80. data/spec/dummy/public/500.html +66 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/models/scim_engine/complex_types/email_spec.rb +23 -0
  83. data/spec/models/scim_engine/resource_type_spec.rb +21 -0
  84. data/spec/models/scim_engine/resources/base_spec.rb +246 -0
  85. data/spec/models/scim_engine/resources/base_validation_spec.rb +61 -0
  86. data/spec/models/scim_engine/resources/user_spec.rb +55 -0
  87. data/spec/models/scim_engine/schema/attribute_spec.rb +80 -0
  88. data/spec/models/scim_engine/schema/base_spec.rb +19 -0
  89. data/spec/models/scim_engine/schema/group_spec.rb +66 -0
  90. data/spec/models/scim_engine/schema/user_spec.rb +140 -0
  91. data/spec/rails_helper.rb +57 -0
  92. data/spec/spec_helper.rb +99 -0
  93. metadata +246 -0
@@ -0,0 +1,13 @@
1
+ module ScimEngine
2
+ module Resources
3
+ class Group < Base
4
+
5
+ set_schema Schema::Group
6
+
7
+ def self.endpoint
8
+ "/Groups"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ScimEngine
2
+ module Resources
3
+ class User < Base
4
+
5
+ set_schema Schema::User
6
+
7
+ def self.endpoint
8
+ "/Users"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,91 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represents an attribute of a SCIM resource that is declared in its schema.
4
+ # Attributes can be simple or complex. A complex attribute needs to have its own schema that is passed to the initilize method when the attribute is instantiated.
5
+ # @example
6
+ # Attribute.new(name: 'userName', type: 'string', uniqueness: 'server')
7
+ # Attribute.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name)
8
+ class Attribute
9
+ include ActiveModel::Model
10
+ include ScimEngine::Errors
11
+ attr_accessor :name, :type, :multiValued, :required, :caseExact, :mutability, :returned, :uniqueness, :subAttributes, :complexType, :canonicalValues
12
+
13
+ # @param options [Hash] a hash of values to be used for instantiating the attribute object. Some of the instance variables of the objects will have default values if this hash does not contain anything for them.
14
+ def initialize(options = {})
15
+ defaults = {
16
+ multiValued: false,
17
+ required: true,
18
+ caseExact: false,
19
+ mutability: 'readWrite',
20
+ uniqueness: 'none',
21
+ returned: 'default',
22
+ canonicalValues: []
23
+ }
24
+
25
+ if options[:complexType]
26
+ defaults.merge!(type: 'complex', subAttributes: options[:complexType].schema.scim_attributes)
27
+ end
28
+
29
+ super(defaults.merge(options || {}))
30
+ end
31
+
32
+ # Validates a value against this attribute object. For simple attributes, it checks if blank is valid or not and if the type matches. For complex attributes, it delegates it to the valid? method of the complex type schema.
33
+ # If the value is not valid, validation message(s) are added to the errors attribute of this object.
34
+ # @return [Boolean] whether or not the value is valid for this attribute
35
+ def valid?(value)
36
+ return valid_blank? if value.blank? && !value.is_a?(FalseClass)
37
+
38
+ if type == 'complex'
39
+ return all_valid?(complexType, value) if multiValued
40
+ valid_complex_type?(value)
41
+ else
42
+ valid_simple_type?(value)
43
+ end
44
+ end
45
+
46
+ def valid_blank?
47
+ return true unless self.required
48
+ errors.add(self.name, "is required")
49
+ false
50
+ end
51
+
52
+ def valid_complex_type?(value)
53
+ if !value.class.respond_to?(:schema) || value.class.schema != complexType.schema
54
+ errors.add(self.name, "has to follow the complexType format.")
55
+ return false
56
+ end
57
+ value.class.schema.valid?(value)
58
+ return true if value.errors.empty?
59
+ add_errors_from_hash(value.errors.to_hash, prefix: self.name)
60
+ false
61
+ end
62
+
63
+ def valid_simple_type?(value)
64
+ valid = (type == 'string' && value.is_a?(String)) ||
65
+ (type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
66
+ (type == 'integer' && (value.is_a?(Integer))) ||
67
+ (type == 'dateTime' && valid_date_time?(value))
68
+ errors.add(self.name, "has the wrong type. It has to be a(n) #{self.type}.") unless valid
69
+ valid
70
+ end
71
+
72
+ def valid_date_time?(value)
73
+ !!Time.iso8601(value)
74
+ rescue ArgumentError
75
+ false
76
+ end
77
+
78
+ def all_valid?(complex_type, value)
79
+ validations = value.map {|value_in_array| valid_complex_type?(value_in_array)}
80
+ validations.all?
81
+ end
82
+
83
+ def as_json(options = {})
84
+ options[:except] ||= ['complexType']
85
+ options[:except] << 'canonicalValues' if canonicalValues.empty?
86
+ super.except(options)
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,35 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # The base class that each schema class must inherit from.
4
+ # These classes represent the schema of a SCIM resource or a complex type that could be used in a resource.
5
+ class Base
6
+ include ActiveModel::Model
7
+ attr_accessor :id, :name, :description, :scim_attributes, :meta
8
+
9
+ def initialize(options = {})
10
+ super
11
+ @meta = Meta.new(resourceType: "Schema")
12
+ end
13
+
14
+ # Converts the schema to its json representation that will be returned by /SCHEMAS end-point of a SCIM service provider.
15
+ def as_json(options = {})
16
+ @meta.location = ScimEngine::Engine.routes.url_helpers.scim_schemas_path(name: id)
17
+ original = super
18
+ original.merge('attributes' => original.delete('scim_attributes'))
19
+ end
20
+
21
+ # Validates the resource against specific validations of each attribute,for example if the type of the attribute matches the one defined in the schema.
22
+ # @param resource [Object] a resource object that uses this schema
23
+ def self.valid?(resource)
24
+ cloned_scim_attributes.each do |scim_attribute|
25
+ resource.add_errors_from_hash(scim_attribute.errors.to_hash) unless scim_attribute.valid?(resource.send(scim_attribute.name))
26
+ end
27
+ end
28
+
29
+ def self.cloned_scim_attributes
30
+ scim_attributes.map { |scim_attribute| scim_attribute.clone }
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module ScimEngine
2
+ module Schema
3
+ module DerivedAttributes
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def set_schema(schema)
8
+ @schema = schema
9
+ derive_attributes_from_schema(schema)
10
+ schema
11
+ end
12
+
13
+ def derive_attributes_from_schema(schema)
14
+ attr_accessor *schema.scim_attributes.map(&:name)
15
+ end
16
+
17
+ def schema
18
+ @schema
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represnts the schema for the Email complex type
4
+ # @see ScimEngine::ComplexTypes::Email
5
+ class Email < Base
6
+ def self.scim_attributes
7
+ @scim_attributes ||= [
8
+ Attribute.new(name: 'value', type: 'string'),
9
+ Attribute.new(name: 'primary', type: 'boolean', required: false),
10
+ Attribute.new(name: 'type', type: 'string', required: false)
11
+ ]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represnts the schema for the Group resource
4
+ # @see ScimEngine::Resources::Group
5
+ class Group < Base
6
+
7
+ def initialize(options = {})
8
+ super(name: 'Group',
9
+ id: self.class.id,
10
+ description: 'Represents a Group',
11
+ scim_attributes: self.class.scim_attributes)
12
+ end
13
+
14
+ def self.id
15
+ 'urn:ietf:params:scim:schemas:core:2.0:Group'
16
+ end
17
+
18
+ def self.scim_attributes
19
+ [
20
+ Attribute.new(name: 'displayName', type: 'string'),
21
+ Attribute.new(name: 'members', multiValued: true, complexType: ScimEngine::ComplexTypes::Reference, mutability: 'readOnly', required: false)
22
+ ]
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represnts the schema for the Name complex type
4
+ # @see ScimEngine::ComplexTypes::Name
5
+ class Name < Base
6
+
7
+ def self.scim_attributes
8
+ @scim_attributes ||= [
9
+ Attribute.new(name: 'familyName', type: 'string'),
10
+ Attribute.new(name: 'givenName', type: 'string'),
11
+ Attribute.new(name: 'formatted', type: 'string', required: false)
12
+ ]
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represnts the schema for the Reference complex type
4
+ # @see ScimEngine::ComplexTypes::Reference
5
+ class Reference < Base
6
+ def self.scim_attributes
7
+ @scim_attributes ||= [
8
+ Attribute.new(name: 'value', type: 'string', mutability: 'readOnly'),
9
+ Attribute.new(name: 'display', type: 'string', mutability: 'readOnly', required: false)
10
+ ]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ module ScimEngine
2
+ module Schema
3
+ # Represnts the schema for the User resource
4
+ # @see ScimEngine::Resources::User
5
+ class User < Base
6
+
7
+ def initialize(options = {})
8
+ super(name: 'User',
9
+ id: self.class.id,
10
+ description: 'Represents a User',
11
+ scim_attributes: self.class.scim_attributes)
12
+
13
+ end
14
+
15
+ def self.id
16
+ 'urn:ietf:params:scim:schemas:core:2.0:User'
17
+ end
18
+
19
+ def self.scim_attributes
20
+ [
21
+ Attribute.new(name: 'userName', type: 'string', uniqueness: 'server'),
22
+ Attribute.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name),
23
+ Attribute.new(name: 'emails', multiValued: true, complexType: ScimEngine::ComplexTypes::Email),
24
+ Attribute.new(name: 'groups', multiValued: true, mutability: 'immutable', complexType: ScimEngine::ComplexTypes::Reference)
25
+ ]
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module ScimEngine
2
+ # Represnts the service provider info. Used by the /ServiceProviderConfig endpoint to privide specification compliance, authentication schemes, data models.
3
+ class ServiceProviderConfiguration
4
+ include ActiveModel::Model
5
+
6
+ attr_accessor :patch, :bulk, :filter, :changePassword,
7
+ :sort, :etag, :authenticationSchemes,
8
+ :schemas, :meta
9
+
10
+ def initialize(attributes = {})
11
+ defaults = {
12
+ bulk: Supportable.unsupported,
13
+ patch: Supportable.unsupported,
14
+ filter: Supportable.unsupported,
15
+ changePassword: Supportable.unsupported,
16
+ sort: Supportable.unsupported,
17
+ etag: Supportable.unsupported,
18
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
19
+ meta: Meta.new(
20
+ resourceType: 'ServiceProviderConfig',
21
+ created: Time.now,
22
+ lastModified: Time.now,
23
+ version: '1'
24
+ ),
25
+ authenticationSchemes: [AuthenticationScheme.basic]
26
+ }
27
+ super(defaults.merge(attributes))
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ module ScimEngine
2
+ class Supportable
3
+ include ActiveModel::Model
4
+ attr_accessor :supported
5
+
6
+ def self.unsupported
7
+ new(supported: false)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>ScimEngine</title>
5
+ <%= stylesheet_link_tag "scim_engine/application", media: "all" %>
6
+ <%= javascript_include_tag "scim_engine/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,6 @@
1
+ ScimEngine::Engine.routes.draw do
2
+ get 'ServiceProviderConfig', to: 'service_provider_configurations#show'
3
+ get 'ResourceTypes', to: 'resource_types#index'
4
+ get 'ResourceTypes/:name', to: 'resource_types#show', as: :scim_resource_type
5
+ get 'Schemas', to: 'schemas#index', as: :scim_schemas
6
+ end
@@ -0,0 +1,13 @@
1
+ require "scim_engine/engine"
2
+
3
+ module ScimEngine
4
+ def self.service_provider_configuration=(custom_configuration)
5
+ @service_provider_configuration = custom_configuration
6
+ end
7
+
8
+ def self.service_provider_configuration(location:)
9
+ @service_provider_configuration ||= ServiceProviderConfiguration.new
10
+ @service_provider_configuration.meta.location = location
11
+ @service_provider_configuration
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ module ScimEngine
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ScimEngine
4
+
5
+ Mime::Type.register "application/scim+json",:scim
6
+
7
+ ActionDispatch::Request.parameter_parsers[Mime::Type.lookup('application/scim+json').symbol] = lambda do |body|
8
+ JSON.parse(body)
9
+ end
10
+
11
+ mattr_accessor :username
12
+ mattr_accessor :password
13
+
14
+ def self.resources
15
+ default_resources + custom_resources
16
+ end
17
+
18
+ # Can be used to add a new resource type which is not provided by the gem.
19
+ # @example
20
+ # module Scim
21
+ # module Resources
22
+ # class ShinyResource < ScimEngine::Resources::Base
23
+ # set_schema Scim::Schema::Shiny
24
+ #
25
+ # def self.endpoint
26
+ # "/Shinies"
27
+ # end
28
+ # end
29
+ # end
30
+ # end
31
+ # ScimEngine::Engine.add_custom_resource Scim::Resources::ShinyResource
32
+ def self.add_custom_resource(resource)
33
+ custom_resources << resource
34
+ end
35
+
36
+ # Returns the list of custom resources, if any.
37
+ def self.custom_resources
38
+ @custom_resources ||= []
39
+ end
40
+
41
+ # Returns the default resources added in this gem: User and Group.
42
+ def self.default_resources
43
+ [ Resources::User, Resources::Group ]
44
+ end
45
+
46
+ def self.schemas
47
+ resources.map(&:schemas).flatten.uniq.map(&:new)
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module ScimEngine
2
+ VERSION = "2.1.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :scim_engine do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,104 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::ApplicationController do
4
+
5
+ context 'custom authentication' do
6
+ before do
7
+ allow(ScimEngine::Engine).to receive(:username) { 'A' }
8
+ allow(ScimEngine::Engine).to receive(:password) { 'B' }
9
+ end
10
+
11
+ controller do
12
+ rescue_from StandardError, with: :handle_resource_not_found
13
+
14
+ def index
15
+ render json: { 'message' => 'cool, cool!' }, format: :scim
16
+ end
17
+ end
18
+
19
+ it 'renders success when valid creds are given' do
20
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'B')
21
+
22
+ get :index, params: { format: :scim }
23
+ expect(response).to be_ok
24
+ expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
25
+ end
26
+
27
+ it 'renders failure with bad password' do
28
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'C')
29
+
30
+ get :index, params: { format: :scim }
31
+ expect(response).not_to be_ok
32
+ end
33
+
34
+ it 'renders failure with bad user name' do
35
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'B')
36
+
37
+ get :index, params: { format: :scim }
38
+ expect(response).not_to be_ok
39
+ end
40
+
41
+
42
+ it 'renders failure with bad user name and password' do
43
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'D')
44
+
45
+ get :index, params: { format: :scim }
46
+ expect(response).not_to be_ok
47
+ end
48
+
49
+
50
+
51
+ end
52
+
53
+
54
+ context 'authenticated' do
55
+ controller do
56
+ rescue_from StandardError, with: :handle_resource_not_found
57
+
58
+ def index
59
+ render json: { 'message' => 'cool, cool!' }, format: :scim
60
+ end
61
+
62
+ def authenticated?
63
+ true
64
+ end
65
+ end
66
+
67
+ describe 'authenticate' do
68
+ it 'renders index if authenticated' do
69
+ get :index, params: { format: :scim }
70
+ expect(response).to be_ok
71
+ expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
72
+ end
73
+
74
+ it 'renders not authorized response if not authenticated' do
75
+ allow(controller).to receive(:authenticated?) { false }
76
+ get :index, params: { format: :scim }
77
+ expect(response).to have_http_status(:unauthorized)
78
+ parsed_body = JSON.parse(response.body)
79
+ expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
80
+ expect(parsed_body).to include('detail' => 'Requires authentication')
81
+ expect(parsed_body).to include('status' => '401')
82
+ end
83
+
84
+ it 'renders resource not found response when resource cannot be found for the given id' do
85
+ allow(controller).to receive(:index).and_raise(StandardError)
86
+ get :index, params: { id: 10, format: :scim }
87
+ expect(response).to have_http_status(:not_found)
88
+ parsed_body = JSON.parse(response.body)
89
+ expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
90
+ expect(parsed_body).to include('detail' => 'Resource 10 not found')
91
+ expect(parsed_body).to include('status' => '404')
92
+ end
93
+ end
94
+
95
+ describe 'require_scim' do
96
+ it 'renders not acceptable if the request does not use scim type' do
97
+ get :index
98
+ expect(response).to have_http_status(:not_acceptable)
99
+
100
+ expect(JSON.parse(response.body)['detail']).to eql('Only application/scim+json type is accepted.')
101
+ end
102
+ end
103
+ end
104
+ end