scim_engine 1.0.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.
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 +19 -0
  13. data/app/models/scim_engine/complex_types/email.rb +11 -0
  14. data/app/models/scim_engine/complex_types/name.rb +7 -0
  15. data/app/models/scim_engine/complex_types/reference.rb +7 -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 +28 -0
  23. data/app/models/scim_engine/resources/base.rb +110 -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 +82 -0
  27. data/app/models/scim_engine/schema/base.rb +30 -0
  28. data/app/models/scim_engine/schema/derived_attributes.rb +24 -0
  29. data/app/models/scim_engine/schema/email.rb +13 -0
  30. data/app/models/scim_engine/schema/group.rb +25 -0
  31. data/app/models/scim_engine/schema/name.rb +15 -0
  32. data/app/models/scim_engine/schema/reference.rb +12 -0
  33. data/app/models/scim_engine/schema/user.rb +28 -0
  34. data/app/models/scim_engine/service_provider_configuration.rb +30 -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/engine.rb +35 -0
  39. data/lib/scim_engine/version.rb +3 -0
  40. data/lib/scim_engine.rb +13 -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 +134 -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/application.rb +16 -0
  59. data/spec/dummy/config/boot.rb +5 -0
  60. data/spec/dummy/config/environment.rb +5 -0
  61. data/spec/dummy/config/environments/development.rb +41 -0
  62. data/spec/dummy/config/environments/production.rb +79 -0
  63. data/spec/dummy/config/environments/test.rb +42 -0
  64. data/spec/dummy/config/initializers/assets.rb +11 -0
  65. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  66. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  67. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  68. data/spec/dummy/config/initializers/inflections.rb +16 -0
  69. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  70. data/spec/dummy/config/initializers/session_store.rb +3 -0
  71. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +23 -0
  74. data/spec/dummy/config/routes.rb +4 -0
  75. data/spec/dummy/config/secrets.yml +22 -0
  76. data/spec/dummy/config.ru +4 -0
  77. data/spec/dummy/log/test.log +863 -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,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
@@ -0,0 +1,23 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::ComplexTypes::Email do
4
+ describe '#as_json' do
5
+ it 'adds work and primary as default' do
6
+ expect(described_class.new.as_json).to eq('type' => 'work', 'primary' => true)
7
+ end
8
+
9
+ it 'allows a custom email type' do
10
+ expect(described_class.new(type: 'home').as_json).to eq('type' => 'home', 'primary' => true)
11
+ end
12
+
13
+ it 'allows a non-primary email' do
14
+ expect(described_class.new(primary: false).as_json).to eq('type' => 'work', 'primary' => false)
15
+ end
16
+
17
+ it 'shows the set email' do
18
+ expect(described_class.new(value: 'a@b.c').as_json).to eq('value' => 'a@b.c', 'type' => 'work', 'primary' => true)
19
+ end
20
+ end
21
+
22
+ end
23
+
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::ResourceType do
4
+ describe '#as_json' do
5
+
6
+ it 'adds the extensionSchemas' do
7
+ resource_type = ScimEngine::ResourceType.new(
8
+ endpoint: '/Gaga',
9
+ schema: 'urn:ietf:params:scim:schemas:core:2.0:User',
10
+ schemaExtensions: ['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']
11
+ )
12
+
13
+ expect(resource_type.as_json['schemaExtensions']).to eql([{
14
+ "schema" => 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
15
+ "required" => false
16
+ }])
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,246 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::Resources::Base do
4
+
5
+ CustomSchema = Class.new(ScimEngine::Schema::Base) do
6
+
7
+ def self.id
8
+ 'custom-id'
9
+ end
10
+
11
+ def self.scim_attributes
12
+ [
13
+ ScimEngine::Schema::Attribute.new(
14
+ name: 'name', complexType: ScimEngine::ComplexTypes::Name, required: false
15
+ ),
16
+ ScimEngine::Schema::Attribute.new(
17
+ name: 'names', multiValued: true, complexType: ScimEngine::ComplexTypes::Name, required: false
18
+ )
19
+ ]
20
+ end
21
+ end
22
+
23
+ CustomResourse = Class.new(ScimEngine::Resources::Base) do
24
+ set_schema CustomSchema
25
+ end
26
+
27
+ describe '#initialize' do
28
+ it 'builds the nested type' do
29
+ resource = CustomResourse.new(name: {
30
+ givenName: 'John',
31
+ familyName: 'Smith'
32
+ })
33
+
34
+ expect(resource.name.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
35
+ expect(resource.name.givenName).to eql('John')
36
+ expect(resource.name.familyName).to eql('Smith')
37
+ end
38
+
39
+ it 'builds an array of nested resources' do
40
+ resource = CustomResourse.new(names: [
41
+ {
42
+ givenName: 'John',
43
+ familyName: 'Smith'
44
+ },
45
+ {
46
+ givenName: 'Jane',
47
+ familyName: 'Snow'
48
+ }
49
+ ])
50
+
51
+ expect(resource.names.is_a?(Array)).to be(true)
52
+ expect(resource.names.first.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
53
+ expect(resource.names.first.givenName).to eql('John')
54
+ expect(resource.names.first.familyName).to eql('Smith')
55
+ expect(resource.names.second.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
56
+ expect(resource.names.second.givenName).to eql('Jane')
57
+ expect(resource.names.second.familyName).to eql('Snow')
58
+ expect(resource.valid?).to be(true)
59
+ end
60
+
61
+ it 'builds an array of nested resources which is invalid if the hash does not follow the schema of the complex type' do
62
+ resource = CustomResourse.new(names: [
63
+ {
64
+ givenName: 'John',
65
+ familyName: 123
66
+ }
67
+ ])
68
+
69
+ expect(resource.names.is_a?(Array)).to be(true)
70
+ expect(resource.names.first.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
71
+ expect(resource.names.first.givenName).to eql('John')
72
+ expect(resource.names.first.familyName).to eql(123)
73
+ expect(resource.valid?).to be(false)
74
+ end
75
+
76
+ end
77
+
78
+ describe '#as_json' do
79
+ it 'renders the json with the resourceType' do
80
+ resource = CustomResourse.new(name: {
81
+ givenName: 'John',
82
+ familyName: 'Smith'
83
+ })
84
+
85
+ result = resource.as_json
86
+ expect(result['schemas']).to eql(['custom-id'])
87
+ expect(result['meta']['resourceType']).to eql('CustomResourse')
88
+ expect(result['errors']).to be_nil
89
+ end
90
+ end
91
+
92
+ describe 'dynamic setters based on schema' do
93
+
94
+ CustomSchema = Class.new(ScimEngine::Schema::Base) do
95
+ def self.scim_attributes
96
+ [
97
+ ScimEngine::Schema::Attribute.new(name: 'customField', type: 'string', required: false),
98
+ ScimEngine::Schema::Attribute.new(name: 'anotherCustomField', type: 'boolean', required: false),
99
+ ScimEngine::Schema::Attribute.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name, required: false)
100
+ ]
101
+ end
102
+ end
103
+
104
+ CustomNameType = Class.new(ScimEngine::ComplexTypes::Base) do
105
+ set_schema ScimEngine::Schema::Name
106
+ end
107
+
108
+ it 'defines a setter for an attribute in the schema' do
109
+ described_class.set_schema CustomSchema
110
+ resource = described_class.new(customField: '100',
111
+ anotherCustomField: true)
112
+ expect(resource.customField).to eql('100')
113
+ expect(resource.anotherCustomField).to eql(true)
114
+ expect(resource.valid?).to be(true)
115
+ end
116
+
117
+ it 'defines a setter for an attribute in the schema' do
118
+ described_class.set_schema CustomSchema
119
+ resource = described_class.new(anotherCustomField: false)
120
+ expect(resource.anotherCustomField).to eql(false)
121
+ expect(resource.valid?).to be(true)
122
+ end
123
+
124
+
125
+ it 'validates that the provided attributes match their schema' do
126
+ described_class.set_schema CustomSchema
127
+ resource = described_class.new(
128
+ name: ScimEngine::ComplexTypes::Name.new(
129
+ givenName: 'John',
130
+ familyName: 'Smith'
131
+ ))
132
+ expect(resource.valid?).to be(true)
133
+ end
134
+
135
+ it 'validates that nested types' do
136
+ described_class.set_schema CustomSchema
137
+ resource = described_class.new(
138
+ name: ScimEngine::ComplexTypes::Name.new(
139
+ givenName: 100,
140
+ familyName: 'Smith'
141
+ ))
142
+ expect(resource.valid?).to be(false)
143
+ end
144
+
145
+
146
+ it 'allows custom complex types as long as the schema matches' do
147
+ described_class.set_schema CustomSchema
148
+ resource = described_class.new(
149
+ name: CustomNameType.new(
150
+ givenName: 'John',
151
+ familyName: 'Smith'
152
+ ))
153
+ expect(resource.valid?).to be(true)
154
+ end
155
+
156
+
157
+ it 'doesnt accept email for a name' do
158
+ described_class.set_schema CustomSchema
159
+ resource = described_class.new(
160
+ name: ScimEngine::ComplexTypes::Email.new(
161
+ value: 'john@smith.com',
162
+ primary: true
163
+ ))
164
+ expect(resource.valid?).to be(false)
165
+ end
166
+
167
+ it 'doesnt accept a complex type for a string' do
168
+ described_class.set_schema CustomSchema
169
+ resource = described_class.new(
170
+ customField: ScimEngine::ComplexTypes::Email.new(
171
+ value: 'john@smith.com',
172
+ primary: true
173
+ ))
174
+ expect(resource.valid?).to be(false)
175
+ end
176
+
177
+ it 'doesnt accept a string for a boolean' do
178
+ described_class.set_schema CustomSchema
179
+ resource = described_class.new(anotherCustomField: 'value')
180
+ expect(resource.valid?).to be(false)
181
+ end
182
+
183
+ end
184
+
185
+ context 'schema extension' do
186
+ customSchema = Class.new(ScimEngine::Schema::Base) do
187
+ def self.id
188
+ 'custom-id'
189
+ end
190
+
191
+ def self.scim_attributes
192
+ [ ScimEngine::Schema::Attribute.new(name: 'name', type: 'string') ]
193
+ end
194
+ end
195
+
196
+ extensionSchema = Class.new(ScimEngine::Schema::Base) do
197
+ def self.id
198
+ 'extension-id'
199
+ end
200
+
201
+ def self.scim_attributes
202
+ [ ScimEngine::Schema::Attribute.new(name: 'relationship', type: 'string') ]
203
+ end
204
+ end
205
+
206
+ let(:resource_class) {
207
+ Class.new(ScimEngine::Resources::Base) do
208
+ set_schema customSchema
209
+ extend_schema extensionSchema
210
+
211
+
212
+ def self.endpoint
213
+ '/gaga'
214
+ end
215
+ def self.resource_type_id
216
+ 'CustomResource'
217
+ end
218
+ end
219
+ }
220
+
221
+ describe '#initialize' do
222
+ it 'allows setting extension attributes' do
223
+ resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
224
+ expect(resource.relationship).to eql('GAGA')
225
+ end
226
+ end
227
+
228
+ describe '#as_json' do
229
+ it 'namespaces the extension attributes' do
230
+ resource = resource_class.new(relationship: 'GAGA')
231
+ hash = resource.as_json
232
+ expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
233
+ expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
234
+ end
235
+ end
236
+
237
+ describe '.resource_type' do
238
+ it 'appends the extension schemas' do
239
+ resource_type = resource_class.resource_type('http://gaga')
240
+ expect(resource_type.meta.location).to eql('http://gaga')
241
+ expect(resource_type.schemaExtensions.count).to eql(1)
242
+ end
243
+ end
244
+
245
+ end
246
+ end
@@ -0,0 +1,61 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::Resources::Base do
4
+
5
+ describe '#valid?' do
6
+ MyCustomSchema = Class.new(ScimEngine::Schema::Base) do
7
+ def self.id
8
+ 'custom-id'
9
+ end
10
+
11
+ def self.scim_attributes
12
+ [
13
+ ScimEngine::Schema::Attribute.new(
14
+ name: 'userName', type: 'string', required: false
15
+ ),
16
+ ScimEngine::Schema::Attribute.new(
17
+ name: 'enforce', type: 'boolean', required: true
18
+ ),
19
+ ScimEngine::Schema::Attribute.new(
20
+ name: 'complexName', complexType: ScimEngine::ComplexTypes::Name, required: false
21
+ ),
22
+ ScimEngine::Schema::Attribute.new(
23
+ name: 'complexNames', complexType: ScimEngine::ComplexTypes::Name, multiValued:true, required: false
24
+ )
25
+ ]
26
+ end
27
+ end
28
+
29
+ MyCustomResource = Class.new(ScimEngine::Resources::Base) do
30
+ set_schema MyCustomSchema
31
+ end
32
+
33
+ it 'adds validation errors to the resource for simple attributes' do
34
+ resource = MyCustomResource.new(userName: 10)
35
+ expect(resource.valid?).to be(false)
36
+ expect(resource.errors.full_messages).to match_array(['Username has the wrong type. It has to be a(n) string.', 'Enforce is required'])
37
+ end
38
+
39
+ it 'adds validation errors to the resource for the complex attribute when the value does not match the schema' do
40
+ resource = MyCustomResource.new(complexName: 10, enforce: false)
41
+ expect(resource.valid?).to be(false)
42
+ expect(resource.errors.full_messages).to match_array(['Complexname has to follow the complexType format.'])
43
+ end
44
+
45
+ it 'adds validation errors to the resource from what the complex type schema returns' do
46
+ resource = MyCustomResource.new(complexName: { givenName: 10 }, enforce: false)
47
+ expect(resource.valid?).to be(false)
48
+ expect(resource.errors.full_messages).to match_array(["Complexname familyname is required", "Complexname givenname has the wrong type. It has to be a(n) string."])
49
+ end
50
+
51
+ it 'adds validation errors to the resource from what the complex type schema returns when it is multi-valued' do
52
+ resource = MyCustomResource.new(complexNames: [
53
+ "Jane Austen",
54
+ { givenName: 'Jane', familyName: true }
55
+ ],
56
+ enforce: false)
57
+ expect(resource.valid?).to be(false)
58
+ expect(resource.errors.full_messages).to match_array(["Complexnames has to follow the complexType format.", "Complexnames familyname has the wrong type. It has to be a(n) string."])
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::Resources::User do
4
+ describe '#name' do
5
+ it 'allows a setter for a valid name' do
6
+ user = described_class.new(name: ScimEngine::ComplexTypes::Name.new(
7
+ familyName: 'Smith',
8
+ givenName: 'John',
9
+ formatted: 'John Smith'
10
+ ))
11
+
12
+ expect(user.name.familyName).to eql('Smith')
13
+ expect(user.name.givenName).to eql('John')
14
+ expect(user.name.formatted).to eql('John Smith')
15
+ expect(user.as_json['name']['errors']).to be_nil
16
+ end
17
+
18
+ it 'validates that the provided name matches the name schema' do
19
+ user = described_class.new(name: ScimEngine::ComplexTypes::Email.new(
20
+ value: 'john@smoth.com',
21
+ primary: true
22
+ ))
23
+
24
+ expect(user.valid?).to be(false)
25
+ end
26
+ end
27
+
28
+ describe '#add_errors_from_hash' do
29
+ let(:user) { described_class.new }
30
+
31
+ it 'adds the error when the value is a string' do
32
+ user.add_errors_from_hash(key: 'some error')
33
+ expect(user.errors.messages).to eql({key: ['some error']})
34
+ expect(user.errors.full_messages).to eql(['Key some error'])
35
+ end
36
+
37
+ it 'adds the error when the value is an array' do
38
+ user.add_errors_from_hash(key: ['error1', 'error2'])
39
+ expect(user.errors.messages).to eql({key: ['error1', 'error2']})
40
+ expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
41
+ end
42
+
43
+ it 'adds the error with prefix when the value is a string' do
44
+ user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
45
+ expect(user.errors.messages).to eql({:'pre.key' => ['some error']})
46
+ expect(user.errors.full_messages).to eql(['Pre key some error'])
47
+ end
48
+
49
+ it 'adds the error wity prefix when the value is an array' do
50
+ user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
51
+ expect(user.errors.messages).to eql({:'pre.key' => ['error1', 'error2']})
52
+ expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::Schema::Attribute do
4
+ describe '#initialize' do
5
+ it 'sets the default properties' do
6
+ attribute = described_class.new
7
+ expect(attribute.multiValued).to be(false)
8
+ expect(attribute.required).to be(true)
9
+ expect(attribute.caseExact).to be(false)
10
+ expect(attribute.mutability).to eql('readWrite')
11
+ expect(attribute.uniqueness).to eql('none')
12
+ expect(attribute.returned).to eql('default')
13
+ end
14
+ it 'lets override the default properties' do
15
+ attribute = described_class.new(multiValued: true)
16
+ expect(attribute.multiValued).to be(true)
17
+ end
18
+
19
+ it 'transforms complexTypes into subAttributes' do
20
+ name = described_class.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name)
21
+ expect(name.type).to eql('complex')
22
+ expect(name.subAttributes).to eql(ScimEngine::Schema::Name.scim_attributes)
23
+ end
24
+
25
+ end
26
+
27
+
28
+ describe '#valid?' do
29
+ it 'is invalid if attribute is required but value is blank' do
30
+ attribute = described_class.new(name: 'userName', type: 'string', required: true)
31
+ expect(attribute.valid?(nil)).to be(false)
32
+ expect(attribute.errors.messages).to eql({userName: ['is required']})
33
+ end
34
+
35
+ it 'is valid if attribute is not required and value is blank' do
36
+ attribute = described_class.new(name: 'userName', type: 'string', required: false)
37
+ expect(attribute.valid?(nil)).to be(true)
38
+ expect(attribute.errors.messages).to eql({})
39
+ end
40
+
41
+ it 'is valid if type is string and given value is string' do
42
+ expect(described_class.new(name: 'name', type: 'string').valid?('something')).to be(true)
43
+ end
44
+
45
+ it 'is invalid if type is string and given value is not string' do
46
+ attribute = described_class.new(name: 'userName', type: 'string')
47
+ expect(attribute.valid?(10)).to be(false)
48
+ expect(attribute.errors.messages).to eql({userName: ['has the wrong type. It has to be a(n) string.']})
49
+ end
50
+
51
+ it 'is valid if type is boolean and given value is boolean' do
52
+ expect(described_class.new(name: 'name', type: 'boolean').valid?(false)).to be(true)
53
+ expect(described_class.new(name: 'name', type: 'boolean').valid?(true)).to be(true)
54
+ end
55
+
56
+ it 'is valid if type is complex and the schema is same' do
57
+ expect(described_class.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name).valid?(ScimEngine::ComplexTypes::Name.new(givenName: 'a', familyName: 'b'))).to be(true)
58
+ end
59
+
60
+ it 'is valid if type is integer and given value is integer (duh)' do
61
+ expect(described_class.new(name: 'quantity', type: 'integer').valid?(123)).to be(true)
62
+ end
63
+
64
+ it 'is not valid if type is integer and given value is not an integer' do
65
+ expect(described_class.new(name: 'quantity', type: 'integer').valid?(123.3)).to be(false)
66
+ end
67
+
68
+ it 'is valid if type is dateTime and given value is an ISO8601 date time' do
69
+ expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('2018-07-26T11:59:43-06:00')).to be(true)
70
+ end
71
+
72
+ it 'is not valid if type is dateTime and given value is a valid date but not in ISO8601 format' do
73
+ expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('2018-07-26')).to be(false)
74
+ end
75
+ it 'is not valid if type is dateTime and given value is not a valid date' do
76
+ expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('gaga')).to be(false)
77
+ end
78
+ end
79
+
80
+ end