flexirest 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.simplecov +4 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +37 -0
- data/CONTRIBUTING.md +62 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +846 -0
- data/Rakefile +13 -0
- data/doc/ActiveRestClient Internals.graffle +1236 -0
- data/doc/ActiveRestClient Internals.png +0 -0
- data/flexirest.gemspec +39 -0
- data/lib/flexirest.rb +25 -0
- data/lib/flexirest/base.rb +189 -0
- data/lib/flexirest/caching.rb +92 -0
- data/lib/flexirest/configuration.rb +209 -0
- data/lib/flexirest/connection.rb +103 -0
- data/lib/flexirest/connection_manager.rb +36 -0
- data/lib/flexirest/headers_list.rb +47 -0
- data/lib/flexirest/instrumentation.rb +62 -0
- data/lib/flexirest/lazy_association_loader.rb +97 -0
- data/lib/flexirest/lazy_loader.rb +23 -0
- data/lib/flexirest/logger.rb +67 -0
- data/lib/flexirest/mapping.rb +69 -0
- data/lib/flexirest/monkey_patching.rb +7 -0
- data/lib/flexirest/proxy_base.rb +193 -0
- data/lib/flexirest/recording.rb +24 -0
- data/lib/flexirest/request.rb +573 -0
- data/lib/flexirest/request_delegator.rb +44 -0
- data/lib/flexirest/request_filtering.rb +62 -0
- data/lib/flexirest/result_iterator.rb +85 -0
- data/lib/flexirest/validation.rb +60 -0
- data/lib/flexirest/version.rb +3 -0
- data/spec/lib/base_spec.rb +389 -0
- data/spec/lib/caching_spec.rb +217 -0
- data/spec/lib/configuration_spec.rb +234 -0
- data/spec/lib/connection_manager_spec.rb +43 -0
- data/spec/lib/connection_spec.rb +159 -0
- data/spec/lib/headers_list_spec.rb +61 -0
- data/spec/lib/instrumentation_spec.rb +58 -0
- data/spec/lib/lazy_association_loader_spec.rb +135 -0
- data/spec/lib/lazy_loader_spec.rb +25 -0
- data/spec/lib/logger_spec.rb +63 -0
- data/spec/lib/mapping_spec.rb +52 -0
- data/spec/lib/proxy_spec.rb +189 -0
- data/spec/lib/recording_spec.rb +34 -0
- data/spec/lib/request_filtering_spec.rb +84 -0
- data/spec/lib/request_spec.rb +711 -0
- data/spec/lib/result_iterator_spec.rb +140 -0
- data/spec/lib/validation_spec.rb +113 -0
- data/spec/lib/xml_spec.rb +74 -0
- data/spec/spec_helper.rb +88 -0
- 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,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
|