flexirest 1.2.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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +4 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +37 -0
  7. data/CONTRIBUTING.md +62 -0
  8. data/Gemfile +4 -0
  9. data/Guardfile +9 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +846 -0
  12. data/Rakefile +13 -0
  13. data/doc/ActiveRestClient Internals.graffle +1236 -0
  14. data/doc/ActiveRestClient Internals.png +0 -0
  15. data/flexirest.gemspec +39 -0
  16. data/lib/flexirest.rb +25 -0
  17. data/lib/flexirest/base.rb +189 -0
  18. data/lib/flexirest/caching.rb +92 -0
  19. data/lib/flexirest/configuration.rb +209 -0
  20. data/lib/flexirest/connection.rb +103 -0
  21. data/lib/flexirest/connection_manager.rb +36 -0
  22. data/lib/flexirest/headers_list.rb +47 -0
  23. data/lib/flexirest/instrumentation.rb +62 -0
  24. data/lib/flexirest/lazy_association_loader.rb +97 -0
  25. data/lib/flexirest/lazy_loader.rb +23 -0
  26. data/lib/flexirest/logger.rb +67 -0
  27. data/lib/flexirest/mapping.rb +69 -0
  28. data/lib/flexirest/monkey_patching.rb +7 -0
  29. data/lib/flexirest/proxy_base.rb +193 -0
  30. data/lib/flexirest/recording.rb +24 -0
  31. data/lib/flexirest/request.rb +573 -0
  32. data/lib/flexirest/request_delegator.rb +44 -0
  33. data/lib/flexirest/request_filtering.rb +62 -0
  34. data/lib/flexirest/result_iterator.rb +85 -0
  35. data/lib/flexirest/validation.rb +60 -0
  36. data/lib/flexirest/version.rb +3 -0
  37. data/spec/lib/base_spec.rb +389 -0
  38. data/spec/lib/caching_spec.rb +217 -0
  39. data/spec/lib/configuration_spec.rb +234 -0
  40. data/spec/lib/connection_manager_spec.rb +43 -0
  41. data/spec/lib/connection_spec.rb +159 -0
  42. data/spec/lib/headers_list_spec.rb +61 -0
  43. data/spec/lib/instrumentation_spec.rb +58 -0
  44. data/spec/lib/lazy_association_loader_spec.rb +135 -0
  45. data/spec/lib/lazy_loader_spec.rb +25 -0
  46. data/spec/lib/logger_spec.rb +63 -0
  47. data/spec/lib/mapping_spec.rb +52 -0
  48. data/spec/lib/proxy_spec.rb +189 -0
  49. data/spec/lib/recording_spec.rb +34 -0
  50. data/spec/lib/request_filtering_spec.rb +84 -0
  51. data/spec/lib/request_spec.rb +711 -0
  52. data/spec/lib/result_iterator_spec.rb +140 -0
  53. data/spec/lib/validation_spec.rb +113 -0
  54. data/spec/lib/xml_spec.rb +74 -0
  55. data/spec/spec_helper.rb +88 -0
  56. metadata +347 -0
@@ -0,0 +1,44 @@
1
+ module Flexirest
2
+ class RequestDelegator < Delegator
3
+ def initialize(obj)
4
+ super
5
+ @delegate_obj = obj
6
+ end
7
+
8
+ def __getobj__
9
+ @delegate_obj
10
+ end
11
+
12
+ def __setobj__(obj)
13
+ @delegate_obj = obj
14
+ end
15
+
16
+ def class
17
+ @delegate_obj.class
18
+ end
19
+
20
+ def method_missing(name, *args, &block)
21
+ # Handles issue with private method 'test' on base Ruby Object
22
+ return @delegate_obj.test if name.to_sym == :test
23
+
24
+ # Forward request to delegate
25
+ @delegate_obj.send(name, *args, &block)
26
+ end
27
+
28
+ def kind_of?(obj)
29
+ @delegate_obj.kind_of?(obj)
30
+ end
31
+
32
+ def is_a?(obj)
33
+ @delegate_obj.is_a?(obj)
34
+ end
35
+
36
+ def instance_of?(obj)
37
+ @delegate_obj.instance_of?(obj)
38
+ end
39
+
40
+ def _delegate?
41
+ return true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ module Flexirest
2
+ module RequestFiltering
3
+ module ClassMethods
4
+ def before_request(method_name = nil, &block)
5
+ @before_filters ||= []
6
+ if block
7
+ @before_filters << block
8
+ elsif method_name
9
+ @before_filters << method_name
10
+ end
11
+ end
12
+
13
+ def after_request(method_name = nil, &block)
14
+ @after_filters ||= []
15
+ if block
16
+ @after_filters << block
17
+ elsif method_name
18
+ @after_filters << method_name
19
+ end
20
+ end
21
+
22
+ def _filter_request(type, name, param)
23
+ _handle_super_class_filters(type, name, param)
24
+ filters = (type == :before ? @before_filters : @after_filters)
25
+ filters ||= []
26
+ filters.each do |filter|
27
+ if filter.is_a? Symbol
28
+ if self.respond_to?(filter)
29
+ self.send(filter, name, param)
30
+ else
31
+ instance = self.new
32
+ instance.send(filter, name, param)
33
+ end
34
+ else
35
+ filter.call(name, param)
36
+ end
37
+ end
38
+ end
39
+
40
+ def _handle_super_class_filters(type, name, request)
41
+ @parents ||= []
42
+ @parents.each do |parent|
43
+ parent._filter_request(type, name, request)
44
+ end
45
+ end
46
+
47
+ def _parents
48
+ @parents ||= []
49
+ end
50
+
51
+ def inherited(subclass)
52
+ subclass._parents << self
53
+ super
54
+ end
55
+ end
56
+
57
+ def self.included(base)
58
+ base.extend(ClassMethods)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ module Flexirest
2
+ class ResultIterator
3
+ include Enumerable
4
+
5
+ attr_accessor :_status
6
+ attr_reader :items, :_headers
7
+
8
+ def initialize(response = nil)
9
+ @_status = response.try :status
10
+ @_headers = response.try :response_headers
11
+ @items = []
12
+ end
13
+
14
+ def <<(item)
15
+ @items << item
16
+ end
17
+
18
+ def size
19
+ @items.size
20
+ end
21
+
22
+ def index(value)
23
+ @items.index(value)
24
+ end
25
+
26
+ def empty?
27
+ size == 0
28
+ end
29
+
30
+ def reverse
31
+ @reversed_items ||= @items.reverse
32
+ end
33
+
34
+ def each
35
+ @items.each do |el|
36
+ yield el
37
+ end
38
+ end
39
+
40
+ def last
41
+ @items.last
42
+ end
43
+
44
+ def [](key)
45
+ @items[key]
46
+ end
47
+
48
+ def shuffle
49
+ @items = @items.shuffle
50
+ self
51
+ end
52
+
53
+ def parallelise(method=nil)
54
+ collected_responses = []
55
+ threads = []
56
+ @items.each do |item|
57
+ threads << Thread.new do
58
+ ret = item.send(method) if method
59
+ ret = yield(item) if block_given?
60
+ Thread.current[:response] = ret
61
+ end
62
+ end
63
+ threads.each do |t|
64
+ t.join
65
+ collected_responses << t[:response]
66
+ end
67
+ collected_responses
68
+ end
69
+
70
+ def paginate(options = {})
71
+ raise WillPaginateNotAvailableException.new unless Object.constants.include?(:WillPaginate)
72
+
73
+ page = options[:page] || 1
74
+ per_page = options[:per_page] || WillPaginate.per_page
75
+ total = options[:total_entries] || @items.length
76
+
77
+ WillPaginate::Collection.create(page, per_page, total) do |pager|
78
+ pager.replace @items[pager.offset, pager.per_page].to_a
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ class WillPaginateNotAvailableException < StandardError ; end
85
+ end
@@ -0,0 +1,60 @@
1
+ module Flexirest
2
+ module Validation
3
+ module ClassMethods
4
+ def validates(field_name, options={}, &block)
5
+ @_validations ||= []
6
+ @_validations << {field_name:field_name, options:options, block:block}
7
+ end
8
+
9
+ def _validations
10
+ @_validations ||= []
11
+ @_validations
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def valid?
20
+ @errors = Hash.new {|h,k| h[k] = []}
21
+ self.class._validations.each do |validation|
22
+ value = self.send(validation[:field_name])
23
+ validation[:options].each do |type, options|
24
+ if type == :presence
25
+ if value.nil?
26
+ @errors[validation[:field_name]] << "must be present"
27
+ end
28
+ elsif type == :length
29
+ if options[:within]
30
+ @errors[validation[:field_name]] << "must be within range #{options[:within]}" unless options[:within].include?(value.to_s.length )
31
+ end
32
+ if options[:minimum]
33
+ @errors[validation[:field_name]] << "must be at least #{options[:minimum]} characters long" unless value.to_s.length >= options[:minimum]
34
+ end
35
+ if options[:maximum]
36
+ @errors[validation[:field_name]] << "must be no more than #{options[:minimum]} characters long" unless value.to_s.length <= options[:maximum]
37
+ end
38
+ elsif type == :numericality
39
+ numeric = (true if Float(value) rescue false)
40
+ @errors[validation[:field_name]] << "must be numeric" unless numeric
41
+ elsif type == :minimum && !value.nil?
42
+ @errors[validation[:field_name]] << "must be at least #{options}" unless value.to_f >= options.to_f
43
+ elsif type == :maximum && !value.nil?
44
+ @errors[validation[:field_name]] << "must be no more than #{options}" unless value.to_f <= options.to_f
45
+ end
46
+ end
47
+ if validation[:block]
48
+ validation[:block].call(self, validation[:field_name], value)
49
+ end
50
+ end
51
+ @errors.empty?
52
+ end
53
+
54
+ def _errors
55
+ @errors ||= Hash.new {|h,k| h[k] = []}
56
+ @errors
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,3 @@
1
+ module Flexirest
2
+ VERSION = "1.2.0"
3
+ end
@@ -0,0 +1,389 @@
1
+ require 'spec_helper'
2
+
3
+ class EmptyExample < Flexirest::Base
4
+ whiny_missing true
5
+ end
6
+
7
+ class TranslatorExample
8
+ def self.all(object)
9
+ ret = {}
10
+ ret["first_name"] = object["name"]
11
+ ret
12
+ end
13
+ end
14
+
15
+ class AlteringClientExample < Flexirest::Base
16
+ translator TranslatorExample
17
+ base_url "http://www.example.com"
18
+
19
+ get :all, "/all", fake:"{\"name\":\"Billy\"}"
20
+ get :list, "/list", fake:"{\"name\":\"Billy\", \"country\":\"United Kingdom\"}"
21
+ get :iterate, "/iterate", fake:"{\"name\":\"Billy\", \"country\":\"United Kingdom\"}"
22
+ get :find, "/find/:id"
23
+ end
24
+
25
+ class RecordResponseExample < Flexirest::Base
26
+ base_url "http://www.example.com"
27
+
28
+ record_response do |url, response|
29
+ raise Exception.new("#{url}|#{response.body}")
30
+ end
31
+
32
+ get :all, "/all"
33
+ end
34
+
35
+ class NonHostnameBaseUrlExample < Flexirest::Base
36
+ base_url "http://www.example.com/v1/"
37
+ get :all, "/all"
38
+ end
39
+
40
+ describe Flexirest::Base do
41
+ it 'should instantiate a new descendant' do
42
+ expect{EmptyExample.new}.to_not raise_error
43
+ end
44
+
45
+ it "should not instantiate a new base class" do
46
+ expect{Flexirest::Base.new}.to raise_error(Exception)
47
+ end
48
+
49
+ it "should save attributes passed in constructor" do
50
+ client = EmptyExample.new(:test => "Something")
51
+ expect(client._attributes[:test]).to be_a(String)
52
+ end
53
+
54
+ it "should allow attribute reading using missing method names" do
55
+ client = EmptyExample.new(:test => "Something")
56
+ expect(client.test).to eq("Something")
57
+ end
58
+
59
+ it "should allow attribute reading using [] array notation" do
60
+ client = EmptyExample.new(:test => "Something")
61
+ expect(client["test"]).to eq("Something")
62
+ end
63
+
64
+ it "allows iteration over attributes using each" do
65
+ client = AlteringClientExample.iterate
66
+ expect(client).to be_respond_to(:each)
67
+ keys = []
68
+ values = []
69
+ client.each do |key, value|
70
+ keys << key ; values << value
71
+ end
72
+ expect(keys).to eq(%w{name country}.map(&:to_sym))
73
+ expect(values).to eq(["Billy", "United Kingdom"])
74
+ end
75
+
76
+ it "should automatically parse ISO 8601 format date and time" do
77
+ t = Time.now
78
+ client = EmptyExample.new(:test => t.iso8601)
79
+ expect(client["test"]).to be_an_instance_of(DateTime)
80
+ expect(client["test"].to_s).to eq(t.to_datetime.to_s)
81
+ end
82
+
83
+ it "should automatically parse ISO 8601 format date and time with milliseconds" do
84
+ t = Time.now
85
+ client = EmptyExample.new(:test => t.iso8601(3))
86
+ expect(client["test"]).to be_an_instance_of(DateTime)
87
+ expect(client["test"].to_s).to eq(t.to_datetime.to_s)
88
+ end
89
+
90
+ it "should automatically parse ISO 8601 format dates" do
91
+ d = Date.today
92
+ client = EmptyExample.new(:test => d.iso8601)
93
+ expect(client["test"]).to be_an_instance_of(Date)
94
+ expect(client["test"]).to eq(d)
95
+ end
96
+
97
+ it "should store attributes set using missing method names and mark them as dirty" do
98
+ client = EmptyExample.new()
99
+ client.test = "Something"
100
+ expect(client.test.to_s).to eq("Something")
101
+ expect(client).to be_dirty
102
+ end
103
+
104
+ it "should store attribute set using []= array notation and mark them as dirty" do
105
+ client = EmptyExample.new()
106
+ client["test"] = "Something"
107
+ expect(client["test"].to_s).to eq("Something")
108
+ expect(client).to be_dirty
109
+ end
110
+
111
+ it "should overwrite attributes already set and mark them as dirty" do
112
+ client = EmptyExample.new(:hello => "World")
113
+ client._clean!
114
+ expect(client).to_not be_dirty
115
+
116
+ client.hello = "Everybody"
117
+ expect(client).to be_dirty
118
+ end
119
+
120
+ it 'should respond_to? attributes defined in the response' do
121
+ client = EmptyExample.new(:hello => "World")
122
+ expect(client.respond_to?(:hello)).to be_truthy
123
+ expect(client.respond_to?(:world)).to be_falsey
124
+ end
125
+
126
+ it "should save the base URL for the API server" do
127
+ class BaseExample < Flexirest::Base
128
+ base_url "https://www.example.com/api/v1"
129
+ end
130
+ expect(BaseExample.base_url).to eq("https://www.example.com/api/v1")
131
+ end
132
+
133
+ it "should allow changing the base_url while running" do
134
+ class OutsideBaseExample < Flexirest::Base ; end
135
+
136
+ Flexirest::Base.base_url = "https://www.example.com/api/v1"
137
+ expect(OutsideBaseExample.base_url).to eq("https://www.example.com/api/v1")
138
+
139
+ Flexirest::Base.base_url = "https://www.example.com/api/v2"
140
+ expect(OutsideBaseExample.base_url).to eq("https://www.example.com/api/v2")
141
+ end
142
+
143
+ it "should include the Mapping module" do
144
+ expect(EmptyExample).to respond_to(:_calls)
145
+ expect(EmptyExample).to_not respond_to(:_non_existant)
146
+ end
147
+
148
+ it "should be able to easily clean all attributes" do
149
+ client = EmptyExample.new(hello:"World", goodbye:"Everyone")
150
+ expect(client).to be_dirty
151
+ client._clean!
152
+ expect(client).to_not be_dirty
153
+ end
154
+
155
+ it "should not overly pollute the instance method namespace to reduce chances of clashing (<10 instance methods)" do
156
+ instance_methods = EmptyExample.instance_methods - Object.methods
157
+ instance_methods = instance_methods - instance_methods.grep(/^_/)
158
+ expect(instance_methods.size).to be < 10
159
+ end
160
+
161
+ it "should raise an exception for missing attributes if whiny_missing is enabled" do
162
+ expect{EmptyExample.new.first_name}.to raise_error(Flexirest::NoAttributeException)
163
+ end
164
+
165
+ it "should be able to lazy instantiate an object from a prefixed lazy_ method call" do
166
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
167
+ example = AlteringClientExample.lazy_find(1)
168
+ expect(example).to be_an_instance_of(Flexirest::LazyLoader)
169
+ expect(example.first_name).to eq("Billy")
170
+ end
171
+
172
+ it "should be able to lazy instantiate an object from a prefixed lazy_ method call from an instance" do
173
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
174
+ example = AlteringClientExample.new.lazy_find(1)
175
+ expect(example).to be_an_instance_of(Flexirest::LazyLoader)
176
+ expect(example.first_name).to eq("Billy")
177
+ end
178
+
179
+ context "#inspect output" do
180
+ it "displays a nice version" do
181
+ object = EmptyExample.new(id: 1, name: "John Smith")
182
+ expect(object.inspect).to match(/#<EmptyExample id: 1, name: "John Smith"/)
183
+ end
184
+
185
+ it "shows dirty attributes as a list of names at the end" do
186
+ object = EmptyExample.new(id: 1, name: "John Smith")
187
+ expect(object.inspect).to match(/#<EmptyExample id: 1, name: "John Smith" \(unsaved: id, name\)/)
188
+ end
189
+
190
+ it "doesn't show an empty list of dirty attributes" do
191
+ object = EmptyExample.new(id: 1, name: "John Smith")
192
+ object.instance_variable_set(:@dirty_attributes, Set.new)
193
+ expect(object.inspect).to_not match(/\(unsaved: id, name\)/)
194
+ end
195
+
196
+ it "shows dates in a nice format" do
197
+ object = EmptyExample.new(dob: Time.new(2015, 01, 02, 03, 04, 05))
198
+ expect(object.inspect).to match(/#<EmptyExample dob: "2015\-01\-02 03:04:05"/)
199
+ end
200
+
201
+ it "shows the etag if one is set" do
202
+ object = EmptyExample.new(id: 1)
203
+ object.instance_variable_set(:@_etag, "sample_etag")
204
+ expect(object.inspect).to match(/#<EmptyExample id: 1, ETag: sample_etag/)
205
+ end
206
+
207
+ it "shows the HTTP status code if one is set" do
208
+ object = EmptyExample.new(id: 1)
209
+ object.instance_variable_set(:@_status, 200)
210
+ expect(object.inspect).to match(/#<EmptyExample id: 1, Status: 200/)
211
+ end
212
+
213
+ it "shows [uninitialized] for new objects" do
214
+ object = EmptyExample.new
215
+ expect(object.inspect).to match(/#<EmptyExample \[uninitialized\]/)
216
+ end
217
+
218
+ end
219
+
220
+ context "accepts a Translator to reformat JSON" do
221
+ it "should log a deprecation warning when using a translator" do
222
+ expect(Flexirest::Logger).to receive(:warn) do |message|
223
+ expect(message).to start_with("DEPRECATION")
224
+ end
225
+ Proc.new do
226
+ class DummyExample < Flexirest::Base
227
+ translator TranslatorExample
228
+ end
229
+ end.call
230
+ end
231
+
232
+ it "should call Translator#method when calling the mapped method if it responds to it" do
233
+ expect(TranslatorExample).to receive(:all).with(an_instance_of(Hash)).and_return({})
234
+ AlteringClientExample.all
235
+ end
236
+
237
+ it "should not raise errors when calling Translator#method if it does not respond to it" do
238
+ expect {AlteringClientExample.list}.to_not raise_error
239
+ end
240
+
241
+ it "should translate JSON returned through the Translator" do
242
+ ret = AlteringClientExample.all
243
+ expect(ret.first_name).to eq("Billy")
244
+ expect(ret.name).to be_nil
245
+ end
246
+
247
+ it "should return original JSON for items that aren't handled by the Translator" do
248
+ ret = AlteringClientExample.list
249
+ expect(ret.name).to eq("Billy")
250
+ expect(ret.first_name).to be_nil
251
+ end
252
+ end
253
+
254
+ context "directly call a URL, rather than via a mapped method" do
255
+ it "should be able to directly call a URL" do
256
+ expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
257
+ EmptyExample._request("http://api.example.com/")
258
+ end
259
+
260
+ it "runs filters as usual" do
261
+ expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
262
+ expect(EmptyExample).to receive(:_filter_request).with(any_args).exactly(2).times
263
+ EmptyExample._request("http://api.example.com/")
264
+ end
265
+
266
+ it "should make an HTTP request" do
267
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
268
+ EmptyExample._request("http://api.example.com/")
269
+ end
270
+
271
+ it "should make an HTTP request including the path in the base_url" do
272
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/v1/all', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
273
+ NonHostnameBaseUrlExample.all
274
+ end
275
+
276
+ it "should map the response from the directly called URL in the normal way" do
277
+ expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
278
+ example = EmptyExample._request("http://api.example.com/")
279
+ expect(example.first_name).to eq("Billy")
280
+ end
281
+
282
+ it "should be able to pass the plain response from the directly called URL bypassing JSON loading" do
283
+ response_body = "This is another non-JSON string"
284
+ expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:response_body)))
285
+ expect(EmptyExample._plain_request("http://api.example.com/", :post, {id:1234})).to eq(response_body)
286
+ end
287
+
288
+ context "Simulating Faraday connection in_parallel" do
289
+ it "should be able to pass the plain response from the directly called URL bypassing JSON loading" do
290
+ response_body = "This is another non-JSON string"
291
+ response = ::FaradayResponseMock.new(
292
+ OpenStruct.new(status:200, response_headers:{}, body:response_body),
293
+ false)
294
+ expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(response)
295
+ result = EmptyExample._plain_request("http://api.example.com/", :post, {id:1234})
296
+
297
+ expect(result).to eq(nil)
298
+
299
+ response.finish
300
+ expect(result).to eq(response_body)
301
+ end
302
+ end
303
+
304
+ it "should cache plain requests separately" do
305
+ perform_caching = EmptyExample.perform_caching
306
+ cache_store = EmptyExample.cache_store
307
+ begin
308
+ response = "This is a non-JSON string"
309
+ other_response = "This is another non-JSON string"
310
+ allow_any_instance_of(Flexirest::Connection).to receive(:get) do |instance, url, others|
311
+ if url == "/?test=1"
312
+ ::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:response))
313
+ else
314
+ ::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:other_response))
315
+ end
316
+ end
317
+ EmptyExample.perform_caching = true
318
+ EmptyExample.cache_store = TestCacheStore.new
319
+ expect(EmptyExample._plain_request("http://api.example.com/?test=1")).to eq(response)
320
+ expect(EmptyExample._plain_request("http://api.example.com/?test=2")).to eq(other_response)
321
+ ensure
322
+ EmptyExample.perform_caching = perform_caching
323
+ EmptyExample.cache_store = cache_store
324
+ end
325
+ end
326
+
327
+ it "should be able to lazy load a direct URL request" do
328
+ expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
329
+ example = EmptyExample._lazy_request("http://api.example.com/")
330
+ expect(example).to be_an_instance_of(Flexirest::LazyLoader)
331
+ expect(example.first_name).to eq("Billy")
332
+ end
333
+
334
+ it "should be able to specify a method and parameters for the call" do
335
+ expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
336
+ EmptyExample._request("http://api.example.com/", :post, {id:1234})
337
+ end
338
+
339
+ it "should be able to use mapped methods to create a request to pass in to _lazy_request" do
340
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
341
+ request = AlteringClientExample._request_for(:find, :id => 1)
342
+ example = AlteringClientExample._lazy_request(request)
343
+ expect(example.first_name).to eq("Billy")
344
+ end
345
+ end
346
+
347
+ context "Recording a response" do
348
+ it "calls back to the record_response callback with the url and response body" do
349
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"Hello world")))
350
+ expect{RecordResponseExample.all}.to raise_error(Exception, "/all|Hello world")
351
+ end
352
+ end
353
+
354
+ context "JSON output" do
355
+ let(:student1) { EmptyExample.new(name:"John Smith", age:31) }
356
+ let(:student2) { EmptyExample.new(name:"Bob Brown", age:29) }
357
+ let(:location) { EmptyExample.new(place:"Room 1408") }
358
+ let(:lazy) { Laz }
359
+ let(:object) { EmptyExample.new(name:"Programming 101", location:location, students:[student1, student2]) }
360
+ let(:json_parsed_object) { MultiJson.load(object.to_json) }
361
+
362
+ it "should be able to export to valid json" do
363
+ expect(object.to_json).to_not be_blank
364
+ expect{MultiJson.load(object.to_json)}.to_not raise_error
365
+ end
366
+
367
+ it "should not be using Object's #to_json method" do
368
+ expect(json_parsed_object["dirty_attributes"]).to be_nil
369
+ end
370
+
371
+ it "should recursively convert nested objects" do
372
+ expect(json_parsed_object["location"]["place"]).to eq(location.place)
373
+ end
374
+
375
+ it "should include arrayed objects" do
376
+ expect(json_parsed_object["students"]).to be_an_instance_of(Array)
377
+ expect(json_parsed_object["students"].size).to eq(2)
378
+ expect(json_parsed_object["students"].first["name"]).to eq(student1.name)
379
+ expect(json_parsed_object["students"].second["name"]).to eq(student2.name)
380
+ end
381
+
382
+ it "should set integers as a native JSON type" do
383
+ expect(json_parsed_object["students"].first["age"]).to eq(student1.age)
384
+ expect(json_parsed_object["students"].second["age"]).to eq(student2.age)
385
+ end
386
+
387
+ end
388
+
389
+ end