daylight 0.9.0.rc1

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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +113 -0
  3. data/app/controllers/daylight_documentation/documentation_controller.rb +27 -0
  4. data/app/helpers/daylight_documentation/documentation_helper.rb +57 -0
  5. data/app/views/daylight_documentation/documentation/_header.haml +4 -0
  6. data/app/views/daylight_documentation/documentation/index.haml +12 -0
  7. data/app/views/daylight_documentation/documentation/model.haml +114 -0
  8. data/app/views/layouts/documentation.haml +22 -0
  9. data/config/routes.rb +8 -0
  10. data/doc/actions.md +70 -0
  11. data/doc/benchmarks.md +17 -0
  12. data/doc/contribute.md +80 -0
  13. data/doc/develop.md +1205 -0
  14. data/doc/environment.md +109 -0
  15. data/doc/example.md +3 -0
  16. data/doc/framework.md +31 -0
  17. data/doc/install.md +128 -0
  18. data/doc/principles.md +42 -0
  19. data/doc/testing.md +107 -0
  20. data/doc/usage.md +970 -0
  21. data/lib/daylight/api.rb +293 -0
  22. data/lib/daylight/associations.rb +247 -0
  23. data/lib/daylight/client_reloader.rb +45 -0
  24. data/lib/daylight/collection.rb +161 -0
  25. data/lib/daylight/errors.rb +94 -0
  26. data/lib/daylight/inflections.rb +7 -0
  27. data/lib/daylight/mock.rb +282 -0
  28. data/lib/daylight/read_only.rb +88 -0
  29. data/lib/daylight/refinements.rb +63 -0
  30. data/lib/daylight/reflection_ext.rb +67 -0
  31. data/lib/daylight/resource_proxy.rb +226 -0
  32. data/lib/daylight/version.rb +10 -0
  33. data/lib/daylight.rb +27 -0
  34. data/rails/daylight/api_controller.rb +354 -0
  35. data/rails/daylight/documentation.rb +13 -0
  36. data/rails/daylight/helpers.rb +32 -0
  37. data/rails/daylight/params.rb +23 -0
  38. data/rails/daylight/refiners.rb +186 -0
  39. data/rails/daylight/server.rb +29 -0
  40. data/rails/daylight/tasks.rb +37 -0
  41. data/rails/extensions/array_ext.rb +9 -0
  42. data/rails/extensions/autosave_association_fix.rb +49 -0
  43. data/rails/extensions/has_one_serializer_ext.rb +111 -0
  44. data/rails/extensions/inflections.rb +6 -0
  45. data/rails/extensions/nested_attributes_ext.rb +94 -0
  46. data/rails/extensions/read_only_attributes.rb +35 -0
  47. data/rails/extensions/render_json_meta.rb +99 -0
  48. data/rails/extensions/route_options.rb +47 -0
  49. data/rails/extensions/versioned_url_for.rb +22 -0
  50. data/spec/config/dependencies.rb +2 -0
  51. data/spec/config/factory_girl.rb +4 -0
  52. data/spec/config/simplecov_rcov.rb +26 -0
  53. data/spec/config/test_api.rb +1 -0
  54. data/spec/controllers/documentation_controller_spec.rb +24 -0
  55. data/spec/dummy/README.rdoc +28 -0
  56. data/spec/dummy/Rakefile +6 -0
  57. data/spec/dummy/app/assets/images/.keep +0 -0
  58. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  59. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  60. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  61. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  62. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  63. data/spec/dummy/app/mailers/.keep +0 -0
  64. data/spec/dummy/app/models/.keep +0 -0
  65. data/spec/dummy/app/models/concerns/.keep +0 -0
  66. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  67. data/spec/dummy/bin/bundle +3 -0
  68. data/spec/dummy/bin/rails +4 -0
  69. data/spec/dummy/bin/rake +4 -0
  70. data/spec/dummy/config/application.rb +24 -0
  71. data/spec/dummy/config/boot.rb +5 -0
  72. data/spec/dummy/config/database.yml +25 -0
  73. data/spec/dummy/config/environment.rb +5 -0
  74. data/spec/dummy/config/environments/development.rb +29 -0
  75. data/spec/dummy/config/environments/production.rb +80 -0
  76. data/spec/dummy/config/environments/test.rb +36 -0
  77. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  78. data/spec/dummy/config/initializers/daylight.rb +1 -0
  79. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  80. data/spec/dummy/config/initializers/inflections.rb +16 -0
  81. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  82. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  83. data/spec/dummy/config/initializers/session_store.rb +3 -0
  84. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  85. data/spec/dummy/config/locales/en.yml +23 -0
  86. data/spec/dummy/config/routes.rb +59 -0
  87. data/spec/dummy/config.ru +4 -0
  88. data/spec/dummy/lib/assets/.keep +0 -0
  89. data/spec/dummy/log/.keep +0 -0
  90. data/spec/dummy/public/404.html +58 -0
  91. data/spec/dummy/public/422.html +58 -0
  92. data/spec/dummy/public/500.html +57 -0
  93. data/spec/dummy/public/favicon.ico +0 -0
  94. data/spec/helpers/documentation_helper_spec.rb +82 -0
  95. data/spec/lib/daylight/api_spec.rb +178 -0
  96. data/spec/lib/daylight/associations_spec.rb +325 -0
  97. data/spec/lib/daylight/collection_spec.rb +235 -0
  98. data/spec/lib/daylight/errors_spec.rb +111 -0
  99. data/spec/lib/daylight/mock_spec.rb +144 -0
  100. data/spec/lib/daylight/read_only_spec.rb +118 -0
  101. data/spec/lib/daylight/refinements_spec.rb +80 -0
  102. data/spec/lib/daylight/reflection_ext_spec.rb +50 -0
  103. data/spec/lib/daylight/resource_proxy_spec.rb +325 -0
  104. data/spec/rails/daylight/api_controller_spec.rb +421 -0
  105. data/spec/rails/daylight/helpers_spec.rb +41 -0
  106. data/spec/rails/daylight/params_spec.rb +45 -0
  107. data/spec/rails/daylight/refiners_spec.rb +178 -0
  108. data/spec/rails/extensions/array_ext_spec.rb +51 -0
  109. data/spec/rails/extensions/has_one_serializer_ext_spec.rb +135 -0
  110. data/spec/rails/extensions/nested_attributes_ext_spec.rb +177 -0
  111. data/spec/rails/extensions/render_json_meta_spec.rb +140 -0
  112. data/spec/rails/extensions/route_options_spec.rb +309 -0
  113. data/spec/rails/extensions/versioned_url_for_spec.rb +46 -0
  114. data/spec/spec_helper.rb +43 -0
  115. data/spec/support/migration_helper.rb +40 -0
  116. metadata +422 -0
@@ -0,0 +1,325 @@
1
+ require 'spec_helper'
2
+
3
+ describe Daylight::Associations do
4
+
5
+ class RelatedTestClass < Daylight::API
6
+ self.password = nil
7
+ self.include_format_in_path = false
8
+ end
9
+
10
+ class AssociationsTestClass < Daylight::API
11
+ self.password = nil
12
+ self.include_format_in_path = false
13
+
14
+ has_many :related_test_classes, through: :associated
15
+ has_many :things, class_name: 'RelatedTestClass', through: :associated
16
+ belongs_to :parent, class_name: 'RelatedTestClass'
17
+ has_one :grandparent, class_name: 'RelatedTestClass', through: :parent
18
+ has_one :associate, class_name: 'RelatedTestClass'
19
+ remote :remote_stuff, class_name: 'RelatedTestClass'
20
+
21
+ def id; 123; end
22
+ def parent_id; 456; end
23
+ end
24
+
25
+ describe :has_many do
26
+
27
+ before do
28
+ data = [{name: 'one'}, {name: 'two'}]
29
+ [RelatedTestClass, AssociationsTestClass].each do |clazz|
30
+ stub_request(:get, %r{#{clazz.site}}).to_return(body: data.to_json)
31
+ end
32
+ end
33
+
34
+
35
+ it "fetches the results out of the attributes if they exist" do
36
+ object = AssociationsTestClass.first
37
+ object.attributes['related_test_classes_attributes'] = ['yay']
38
+ collection = object.related_test_classes
39
+ collection.should be_instance_of(Array)
40
+ collection.first.should == 'yay'
41
+ end
42
+
43
+ describe "with new resource" do
44
+ let(:new_resource) { AssociationsTestClass.new }
45
+
46
+
47
+ it "caches the results of the association" do
48
+ collection = new_resource.related_test_classes
49
+ new_resource.related_test_classes.should == collection
50
+ end
51
+
52
+ it "creates a method that construts an Array for the collection" do
53
+ collection = new_resource.related_test_classes
54
+
55
+ collection.should be_a Array
56
+ end
57
+
58
+ it "sets the associations directly the attributes hash" do
59
+ new_resource.related_test_classes = ["associated instances"]
60
+
61
+ new_resource.attributes['related_test_classes_attributes'].should == ["associated instances"]
62
+ end
63
+
64
+ it "fetches the stored associations out of the attributes when they are set" do
65
+ new_resource.related_test_classes = ["associated instances"]
66
+
67
+ new_resource.related_test_classes.should == ["associated instances"]
68
+ end
69
+ end
70
+
71
+
72
+ describe "with existing resource" do
73
+ let(:existing_resource) { AssociationsTestClass.first }
74
+
75
+ it "creates a method that construts an ResourceProxy for the association" do
76
+ proxy = existing_resource.related_test_classes
77
+
78
+ proxy.should be_a Daylight::ResourceProxy
79
+ proxy.resource_class.should == RelatedTestClass
80
+ end
81
+
82
+ it "creates a method that construts an ResourceProxy with context about the association" do
83
+ proxy = existing_resource.related_test_classes
84
+
85
+ proxy.association_name.should == :related_test_classes
86
+ proxy.association_resource.should == existing_resource
87
+ end
88
+
89
+ it "hits the association api endpoint" do
90
+ existing_resource.related_test_classes.load
91
+
92
+ a_request(:get, "http://daylight.test/v1/associations_test_classes/123/related_test_classes.json").should have_been_made
93
+ end
94
+
95
+ it "caches the results of the association" do
96
+ proxy = existing_resource.related_test_classes
97
+ existing_resource.related_test_classes.should == proxy
98
+ end
99
+
100
+ it "supports the :class_name option" do ## THIS ONE
101
+ proxy = existing_resource.things
102
+ proxy.resource_class.should == RelatedTestClass
103
+ end
104
+
105
+ it "chains using the proxy class for the associated model" do
106
+ proxy = existing_resource.related_test_classes.where(wibble: 'wobble')
107
+
108
+ proxy.resource_class.should == RelatedTestClass
109
+ proxy.to_params[:filters].should == {wibble: 'wobble'}
110
+ end
111
+
112
+ it "sets the associations directly the attributes hash" do
113
+ existing_resource.related_test_classes = ["associated instances"]
114
+
115
+ existing_resource.attributes['related_test_classes_attributes'].should == ["associated instances"]
116
+ end
117
+
118
+ it "fetches the stored associations out of the attributes when they exist" do
119
+ existing_resource.related_test_classes = ["associated instances"]
120
+
121
+ existing_resource.related_test_classes.should == ["associated instances"]
122
+ end
123
+ end
124
+ end
125
+
126
+ describe :belongs_to do
127
+
128
+ before do
129
+ data = { parent: {name: 'three'}}
130
+ [RelatedTestClass, AssociationsTestClass].each do |clazz|
131
+ stub_request(:get, %r{#{clazz.site}}).to_return(body: data.to_json)
132
+ end
133
+ end
134
+
135
+ it 'still fetches the parent object' do
136
+ resource = AssociationsTestClass.find(1)
137
+
138
+ resource.parent.should_not be_nil
139
+ resource.parent.name.should == 'three'
140
+ end
141
+
142
+ it 'sets the parent to a new object' do
143
+ resource = AssociationsTestClass.find(1)
144
+ resource.parent = RelatedTestClass.new(name: 'new parent')
145
+
146
+ resource.parent.name.should == 'new parent'
147
+ end
148
+
149
+ it 'sets the parent foreign key' do
150
+ resource = AssociationsTestClass.find(1)
151
+ resource.parent = RelatedTestClass.new(id: 789, name: 'new parent')
152
+
153
+ resource.attributes['parent_id'].should == 789
154
+ end
155
+
156
+ it 'sets the parent directly in the nested attributes hash' do
157
+ resource = AssociationsTestClass.find(1)
158
+ resource.parent = RelatedTestClass.new(id: 789, name: 'new parent')
159
+
160
+ resource.attributes['parent_attributes'].should == resource.parent
161
+ end
162
+ end
163
+
164
+ describe :belongs_to_through do
165
+
166
+ before do
167
+ association_data = { through: {
168
+ id: 1,
169
+ parent_id: 456, # ignored because of parent_id method
170
+ parent_attributes: {
171
+ id: 456,
172
+ grandparent_id: 3
173
+ }
174
+ }
175
+ }
176
+
177
+ embedded_data = { through: {
178
+ id: 2,
179
+ parent_id: 456, # ignored because of parent_id method
180
+ parent_attributes: {
181
+ id: 456,
182
+ grandparent: { id: 4, name: 'embed' }
183
+ }
184
+ }
185
+ }
186
+
187
+ related_data = {id: nil, name: 'related'}
188
+
189
+ stub_request(:get, %r{#{AssociationsTestClass.element_path(1)}}).to_return(body: association_data.to_json)
190
+ stub_request(:get, %r{#{AssociationsTestClass.element_path(2)}}).to_return(body: embedded_data.to_json)
191
+ stub_request(:get, %r{#{RelatedTestClass.element_path(456)}}).to_return(body: related_data.merge(id: 456).to_json)
192
+ stub_request(:get, %r{#{RelatedTestClass.element_path(3)}}).to_return(body: related_data.merge(id: 3).to_json)
193
+ end
194
+
195
+ it 'still fetches the parent object' do
196
+ resource = AssociationsTestClass.find(1)
197
+
198
+ resource.parent.should_not be_nil
199
+ resource.parent.id.should == 456
200
+ resource.parent.name.should == 'related'
201
+ end
202
+
203
+ it 'fetches the "through" object' do
204
+ resource = AssociationsTestClass.find(1)
205
+
206
+ resource.grandparent.should_not be_nil
207
+ resource.grandparent.id.should == 3
208
+ resource.grandparent.name.should == 'related'
209
+ end
210
+
211
+ it 'fetches embedded "through" object' do
212
+ resource = AssociationsTestClass.find(2)
213
+
214
+ resource.grandparent.should be_kind_of(ActiveResource::Base)
215
+ resource.grandparent.id.should == 4
216
+ resource.grandparent.name.should == 'embed'
217
+ end
218
+
219
+ it 'sets the "through" object foreign key' do
220
+ resource = AssociationsTestClass.find(1)
221
+ resource.grandparent = RelatedTestClass.new(id: 789, name: 'new grandparent')
222
+
223
+ resource.attributes['parent_attributes']['grandparent_id'].should == 789
224
+ end
225
+
226
+ it 'sets the through object directly in the nested attributes hash' do
227
+ resource = AssociationsTestClass.find(1)
228
+ resource.grandparent = RelatedTestClass.new(id: 789, name: 'new grandparent')
229
+
230
+ resource.attributes['parent_attributes']['grandparent_attributes'].should == resource.grandparent
231
+ end
232
+ end
233
+
234
+ describe :has_one do
235
+ before do
236
+ associated = { id: nil, name: 'Hardy', associate_attributes: { id: 100 } }
237
+ related = { id: 100, name: 'Laurel' }
238
+ stub_request(:get, %r{#{AssociationsTestClass.element_path(1)}}).to_return(body: associated.to_json)
239
+ # It uses the filter method instead of default ActiveResource behavior
240
+ # http://daylight.test/v1/related_test_classes?filters%5Bassociations_test_class_id%5D=123&limit=1
241
+ stub_request(:get, %r{filters%5Bassociations_test_class_id%5D=123}).to_return(body: [related].to_json)
242
+ end
243
+
244
+ it 'still fetches the associate object' do
245
+ resource = AssociationsTestClass.find(1)
246
+
247
+ resource.associate.should_not be_nil
248
+ resource.associate.id.should == 100
249
+ resource.associate.name.should == 'Laurel'
250
+ end
251
+
252
+ it 'sets the associate to a new object' do
253
+ resource = AssociationsTestClass.find(1)
254
+ resource.associate = RelatedTestClass.new(name: 'Rik Mayall')
255
+
256
+ resource.associate.name.should == 'Rik Mayall'
257
+ end
258
+
259
+ it 'sets the associate foreign key' do
260
+ resource = AssociationsTestClass.find(1)
261
+ resource.associate = RelatedTestClass.new(id: 333, name: 'Rik Mayall')
262
+
263
+ resource.associate.associations_test_class_id.should == resource.id
264
+ end
265
+
266
+ it 'sets the associate directly in the nested attributes hash' do
267
+ resource = AssociationsTestClass.find(1)
268
+ resource.associate = RelatedTestClass.new(id: 333, name: 'Rik Mayall')
269
+
270
+ resource.attributes['associate_attributes'].should == resource.associate
271
+ end
272
+ end
273
+
274
+ describe :remote do
275
+
276
+ def respond_with(data)
277
+ stub_request(:get, %r{#{AssociationsTestClass.site}}).to_return(body: data.to_json)
278
+ end
279
+
280
+ let(:subject) { AssociationsTestClass.new }
281
+
282
+ it "loads data from the remote" do
283
+ respond_with({remote_stuff: {id: 2, foo: 'bar'}})
284
+
285
+ subject.remote_stuff.foo.should == 'bar'
286
+ end
287
+
288
+ it "handles collections" do
289
+ respond_with({remote_stuff: [{id: 2, foo: 'first'}, {id: 3, foo: 'second'}]})
290
+
291
+ subject.remote_stuff.first.foo.should == 'first'
292
+ subject.remote_stuff.last.foo.should == 'second'
293
+ end
294
+
295
+ it "caches the data" do
296
+ respond_with({remote_stuff: {cache: 'cachey cache'}})
297
+
298
+ subject.should_receive(:get).once.and_call_original
299
+
300
+ subject.remote_stuff.cache.should == 'cachey cache'
301
+ subject.remote_stuff.cache.should == 'cachey cache'
302
+ end
303
+
304
+ it "handles metadata with an object" do
305
+ respond_with({remote_stuff: {id: 2, foo: 'bar'}, meta: {}})
306
+
307
+ subject.remote_stuff.foo.should == 'bar'
308
+ end
309
+
310
+ it "handles metadata with a collection" do
311
+ respond_with({remote_stuff: [{id: 2, foo: 'first'}, {id: 3, foo: 'second'}], meta: {}})
312
+
313
+ subject.remote_stuff.first.foo.should == 'first'
314
+ subject.remote_stuff.last.foo.should == 'second'
315
+ end
316
+
317
+ it "returns data from the attributes if that already exists" do
318
+ subject.attributes[:remote_stuff] = 'wibble'
319
+
320
+ subject.remote_stuff.should == 'wibble'
321
+ end
322
+
323
+ end
324
+
325
+ end
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+
3
+ describe Daylight::Collection do
4
+
5
+ class CollectionTestClass < Daylight::API
6
+ self.password = nil
7
+
8
+ scopes :foo, :bar
9
+ end
10
+
11
+ before do
12
+ stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: [].to_json)
13
+ end
14
+
15
+ describe :metadata do
16
+ before do
17
+ data = { collection: [{name: 'one'}, {name: 'two'}], meta: {data: 'baz'} }
18
+ stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
19
+ end
20
+
21
+ it 'is retrieved before parsing' do
22
+ collection = CollectionTestClass.all
23
+
24
+ collection.metadata.should == {'data' => 'baz'}
25
+ end
26
+
27
+ it 'is passed to child elements' do
28
+ collection = CollectionTestClass.all
29
+ collection.each do |child|
30
+ child.metadata.should == {'data' => 'baz'}
31
+ end
32
+ end
33
+ end
34
+
35
+ describe :first_or_initialize do
36
+
37
+ describe 'with results' do
38
+ before do
39
+ data = [{name: 'one'}, {name: 'two'}]
40
+ stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
41
+ end
42
+
43
+ it 'returns the first result' do
44
+ result = CollectionTestClass.where(name: 'one').first_or_initialize
45
+ result.should be_kind_of(CollectionTestClass)
46
+ result.name.should == 'one'
47
+ end
48
+ end
49
+
50
+ it 'reraises NoMethodErrors on initialize' do
51
+ CollectionTestClass.stub(:new).and_raise(NoMethodError)
52
+
53
+ expect { CollectionTestClass.where(name: 'one').first_or_initialize }.to \
54
+ raise_error(StandardError, 'Cannot create resource from resource type: CollectionTestClass')
55
+ end
56
+
57
+ it 'returns an unsaved instance' do
58
+ result = CollectionTestClass.where(name: 'two').first_or_initialize
59
+
60
+ result.should be_kind_of(CollectionTestClass)
61
+ result.should_not be_persisted
62
+ end
63
+
64
+ it 'adds known parameter values to attributes' do
65
+ result = CollectionTestClass.where(name: 'two').first_or_initialize
66
+
67
+ result.should be_kind_of(CollectionTestClass)
68
+ result.should_not be_persisted
69
+
70
+ result.name.should == 'two'
71
+ end
72
+
73
+ it 'adds query parameter to prefix_options' do
74
+ result = CollectionTestClass.foo.bar.where(name: 'two').first_or_initialize
75
+
76
+ result.should be_kind_of(CollectionTestClass)
77
+ result.should_not be_persisted
78
+
79
+ result.prefix_options.should == {scopes: [:foo, :bar]}
80
+ end
81
+
82
+ it 'keeps collection parameters from being merged into attributes' do
83
+ result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_initialize
84
+
85
+ result.should be_kind_of(CollectionTestClass)
86
+ result.should_not be_persisted
87
+
88
+ result.attributes[:limit].should be_nil
89
+ result.attributes[:offset].should be_nil
90
+ result.attributes[:order].should be_nil
91
+ end
92
+
93
+ it 'leaves unknown paramters so they are merged into attributes' do
94
+ result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_initialize
95
+
96
+ result.should be_kind_of(CollectionTestClass)
97
+ result.should_not be_persisted
98
+
99
+ result.a.should == 1
100
+ result.b.should == 2
101
+ end
102
+ end
103
+
104
+ describe :first_or_create do
105
+
106
+ describe 'with results' do
107
+ before do
108
+ data = [{name: 'one'}, {name: 'two'}]
109
+ stub_request(:get, %r{#{CollectionTestClass.site}}).to_return(body: data.to_json)
110
+ end
111
+
112
+ it 'returns the first result' do
113
+ result = CollectionTestClass.where(name: 'one').first_or_create
114
+ result.should be_kind_of(CollectionTestClass)
115
+ result.name.should == 'one'
116
+ end
117
+ end
118
+
119
+ describe 'with errors' do
120
+ before do
121
+ errors = {errors: {status: ["can't be blank", "is not included in the list"]} }
122
+ stub_request(:post, %r{#{CollectionTestClass.site}}).to_return(body: errors.to_json, status: 422)
123
+ end
124
+
125
+ it 'returns an unsaved instance with errors' do
126
+ result = CollectionTestClass.where(name: 'one').first_or_create
127
+ result.should be_kind_of(CollectionTestClass)
128
+ result.should_not be_persisted
129
+
130
+ result.name.should == 'one'
131
+ result.errors.full_messages.should == ["Status can't be blank", "Status is not included in the list"]
132
+ end
133
+
134
+ it 'reraises NoMethodErrors on initialize' do
135
+ CollectionTestClass.stub(:new).and_raise(NoMethodError)
136
+
137
+ expect { CollectionTestClass.where(name: 'one').first_or_create }.to \
138
+ raise_error(StandardError, 'Cannot build resource from resource type: CollectionTestClass')
139
+ end
140
+ end
141
+
142
+ describe 'with create' do
143
+ before do
144
+ resource = {id: 1, name: 'one'}
145
+ stub_request(:post, %r{#{CollectionTestClass.site}}).to_return(body: resource.to_json, status: 201)
146
+ end
147
+
148
+ it 'returns a saved instance' do
149
+ result = CollectionTestClass.where(name: 'one').first_or_create
150
+
151
+ result.should be_kind_of(CollectionTestClass)
152
+ result.should be_persisted
153
+
154
+ result.id.should == 1
155
+ end
156
+
157
+ it 'adds known parameter values to attributes' do
158
+ result = CollectionTestClass.where(name: 'two').first_or_create
159
+
160
+ result.should be_kind_of(CollectionTestClass)
161
+ result.should be_persisted
162
+
163
+ result.name.should == 'one'
164
+ end
165
+
166
+ describe 'before save' do
167
+ before do
168
+ CollectionTestClass.any_instance.stub(save: true)
169
+ end
170
+
171
+ it 'adds query parameter to prefix_options before save' do
172
+ result = CollectionTestClass.foo.bar.where(name: 'two').first_or_initialize
173
+
174
+ result.should be_kind_of(CollectionTestClass)
175
+ result.should_not be_persisted
176
+
177
+ result.prefix_options.should == {scopes: [:foo, :bar]}
178
+ end
179
+
180
+ it 'keeps collection parameters from being merged into attributes' do
181
+ result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_create
182
+
183
+ result.should be_kind_of(CollectionTestClass)
184
+ result.should_not be_persisted
185
+
186
+ result.attributes[:limit].should be_nil
187
+ result.attributes[:offset].should be_nil
188
+ result.attributes[:order].should be_nil
189
+ end
190
+
191
+ it 'keeps unknown paramters so they are merged into attributes' do
192
+ result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_create
193
+
194
+ result.should be_kind_of(CollectionTestClass)
195
+ result.should_not be_persisted
196
+
197
+ result.a.should == 1
198
+ result.b.should == 2
199
+ end
200
+ end
201
+
202
+ describe 'after save' do
203
+ it 'has no prefix_options' do
204
+ result = CollectionTestClass.foo.bar.where(name: 'two').first_or_create
205
+
206
+ result.should be_kind_of(CollectionTestClass)
207
+ result.should be_persisted
208
+
209
+ result.prefix_options.should == {}
210
+ end
211
+
212
+ it 'has no collection parameters' do
213
+ result = CollectionTestClass.where(name: 'two').limit(10).offset(100).order(name: 'asc').first_or_create
214
+
215
+ result.should be_kind_of(CollectionTestClass)
216
+ result.should be_persisted
217
+
218
+ result.attributes[:limit].should be_nil
219
+ result.attributes[:offset].should be_nil
220
+ result.attributes[:order].should be_nil
221
+ end
222
+
223
+ it 'keeps unknown paramters' do
224
+ result = CollectionTestClass.find(:all, params: {a: 1, b: 2}).first_or_create
225
+
226
+ result.should be_kind_of(CollectionTestClass)
227
+ result.should be_persisted
228
+
229
+ result.a.should == 1
230
+ result.b.should == 2
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Daylight::Errors do
4
+
5
+ class BaseErrorTest < StandardError
6
+ def initialize resposne, message=nill
7
+ @message = 'base'
8
+ end
9
+
10
+ def to_s
11
+ @message.dup
12
+ end
13
+ end
14
+
15
+ class ErrorTest < BaseErrorTest
16
+ include Daylight::Errors
17
+ end
18
+
19
+ def mock_response headers, body
20
+ double(header: headers, body: body)
21
+ end
22
+
23
+ it 'ignores parsing with no body and content-type' do
24
+ error = ErrorTest.new(mock_response({}, nil))
25
+
26
+ error.messages.should == []
27
+ error.to_s.should == 'base'
28
+ end
29
+
30
+ it 'sets no message for unknown content-type' do
31
+ error = ErrorTest.new(mock_response({'content-type' => 'application/foo'}, 'bar error, no drink'))
32
+
33
+ error.messages.should == []
34
+ error.to_s.should == 'base'
35
+ end
36
+
37
+
38
+ describe :xml do
39
+ let(:xml_error) { '<errors><error>message</error></errors>' }
40
+ let(:xml_errors) { '<errors><error>problem 1</error><error>problem 2</error></errors>' }
41
+
42
+ def xml_response body=nil
43
+ mock_response({'content-type' => 'application/xml; charset=utf-8'}, body)
44
+ end
45
+
46
+ it 'parses no error' do
47
+ error = ErrorTest.new(xml_response)
48
+
49
+ error.messages.should == []
50
+ error.to_s.should == 'base'
51
+ end
52
+
53
+ it 'parses one error' do
54
+ error = ErrorTest.new(xml_response(xml_error))
55
+
56
+ error.messages.should == ['message']
57
+ error.to_s.should == 'base Root Cause = message'
58
+ end
59
+
60
+ it 'parses multiple errors' do
61
+ error = ErrorTest.new(xml_response(xml_errors))
62
+
63
+ error.messages.should == ['problem 1', 'problem 2']
64
+ error.to_s.should == 'base Root Cause = problem 1, problem 2'
65
+ end
66
+
67
+ it 'handles decode errors and sets no messages' do
68
+ error = ErrorTest.new(xml_response('bad data'))
69
+
70
+ error.messages.should == []
71
+ error.to_s.should == 'base'
72
+ end
73
+ end
74
+
75
+ describe :json do
76
+ let(:json_error) { { errors: 'message'}.to_json }
77
+ let(:json_errors) { { errors: ['problem 1', 'problem 2'] }.to_json }
78
+
79
+ def json_response body=nil
80
+ mock_response({'content-type' => 'application/json; charset=utf-8'}, body)
81
+ end
82
+
83
+ it 'parses no error' do
84
+ error = ErrorTest.new(json_response)
85
+
86
+ error.messages.should == []
87
+ error.to_s.should == 'base'
88
+ end
89
+
90
+ it 'parses one error' do
91
+ error = ErrorTest.new(json_response(json_error))
92
+
93
+ error.messages.should == ['message']
94
+ error.to_s.should == 'base Root Cause = message'
95
+ end
96
+
97
+ it 'parses multiple errors' do
98
+ error = ErrorTest.new(json_response(json_errors))
99
+
100
+ error.messages.should == ['problem 1', 'problem 2']
101
+ error.to_s.should == 'base Root Cause = problem 1, problem 2'
102
+ end
103
+
104
+ it 'handles decode errors and sets no messages' do
105
+ error = ErrorTest.new(json_response('<bad data>'))
106
+
107
+ error.messages.should == []
108
+ error.to_s.should == 'base'
109
+ end
110
+ end
111
+ end