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,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flexirest::ConnectionManager do
4
+ before(:each) do
5
+ Flexirest::ConnectionManager.reset!
6
+ end
7
+
8
+ it "should have a get_connection method" do
9
+ expect(Flexirest::ConnectionManager).to respond_to("get_connection")
10
+ end
11
+
12
+ it "should return a connection for a given base url" do
13
+ connection = Flexirest::ConnectionManager.get_connection("http://www.example.com")
14
+ expect(connection).to be_kind_of(Flexirest::Connection)
15
+ end
16
+
17
+ it "should return the same connection for each base url when re-requested" do
18
+ connection = Flexirest::ConnectionManager.get_connection("http://www.example.com")
19
+ expect(Flexirest::ConnectionManager.get_connection("http://www.example.com")).to eq(connection)
20
+ end
21
+
22
+ it "should return different connections for each base url when requested" do
23
+ base_url = "http://www.example.com"
24
+ other_base_url = "http://other.example.com"
25
+ expect(Flexirest::ConnectionManager.get_connection(base_url).base_url).to eq(base_url)
26
+ expect(Flexirest::ConnectionManager.get_connection(other_base_url).base_url).to eq(other_base_url)
27
+ expect(Thread.current[:_connections].size).to eq(2)
28
+ end
29
+
30
+ it "should find a connection if you pass in URLs containing an existing connection's base_url" do
31
+ base_url = "http://www.example.com"
32
+ connection = Flexirest::ConnectionManager.get_connection(base_url)
33
+ found_connection = Flexirest::ConnectionManager.find_connection_for_url("#{base_url}:8080/people/test")
34
+ expect(found_connection).to eq(connection)
35
+ end
36
+
37
+ it "should call 'in_parllel' for a session and yield procedure inside that block" do
38
+ Flexirest::Base.adapter = :typhoeus
39
+ session = Flexirest::ConnectionManager.get_connection("http://www.example.com").session
40
+ expect { |b| Flexirest::ConnectionManager.in_parallel("http://www.example.com", &b)}.to yield_control
41
+ Flexirest::Base._reset_configuration!
42
+ end
43
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flexirest::Connection do
4
+ before do
5
+ @connection = Flexirest::Connection.new("http://www.example.com")
6
+ end
7
+
8
+ after do
9
+ Flexirest::Base._reset_configuration!
10
+ @connection.reconnect
11
+ end
12
+
13
+ it "should contain a Farday connection" do
14
+ expect(@connection.session).to be_a_kind_of(Faraday::Connection)
15
+ end
16
+
17
+ it "should set the Base URL to be the one passed in" do
18
+ expect(@connection.session.url_prefix.to_s).to eq("http://www.example.com/")
19
+ end
20
+
21
+ it "should set a user agent for the session" do
22
+ expect(@connection.headers["User-Agent"]).to match(/^Flexirest\/[0-9.]+$/)
23
+ end
24
+
25
+ it "should try to Keep-Alive session connections" do
26
+ expect(@connection.headers["Connection"]).to match(/Keep-Alive/)
27
+ end
28
+
29
+ it "should pass a GET request through to Faraday" do
30
+ stub_request(:get, "www.example.com/foo").to_return(body: "{result:true}")
31
+ result = @connection.get("/foo")
32
+ expect(result.body).to eq("{result:true}")
33
+ end
34
+
35
+ it "should pass a PUT request through to Faraday" do
36
+ stub_request(:put, "www.example.com/foo").with(body: "body").to_return(body: "{result:true}")
37
+ result = @connection.put("/foo", "body")
38
+ expect(result.body).to eq("{result:true}")
39
+ end
40
+
41
+ it "should pass a POST request through to Faraday" do
42
+ stub_request(:post, "www.example.com/foo").with(body: "body").to_return(body: "{result:true}")
43
+ result = @connection.post("/foo", "body")
44
+ expect(result.body).to eq("{result:true}")
45
+ end
46
+
47
+ it "should pass a DELETE request through to Faraday" do
48
+ stub_request(:delete, "www.example.com/foo").to_return(body: "{result:true}")
49
+ result = @connection.delete("/foo")
50
+ expect(result.body).to eq("{result:true}")
51
+ end
52
+
53
+ describe "with default Faraday headers" do
54
+ before do
55
+ @default_headers = { "User-Agent" => "Custom" }
56
+
57
+ Flexirest::Base.faraday_config do |faraday|
58
+ faraday.adapter Flexirest::Base.adapter
59
+ faraday.headers.update(@default_headers)
60
+ end
61
+ @connection.reconnect
62
+ end
63
+
64
+ it "should pass a GET request through to Faraday preserving headers" do
65
+ stub_request(:get, "www.example.com/foo").
66
+ with(:headers => @default_headers).
67
+ to_return(body: "{result:true}")
68
+
69
+ result = @connection.get("/foo")
70
+ expect(result.body).to eq("{result:true}")
71
+ end
72
+
73
+ it "should pass a PUT request through to Faraday" do
74
+ stub_request(:put, "www.example.com/foo").
75
+ with(body: "body").
76
+ to_return(body: "{result:true}", :headers => @default_headers)
77
+
78
+ result = @connection.put("/foo", "body")
79
+ expect(result.body).to eq("{result:true}")
80
+ end
81
+
82
+ it "should pass a POST request through to Faraday" do
83
+ stub_request(:post, "www.example.com/foo").
84
+ with(body: "body", :headers => @default_headers).
85
+ to_return(body: "{result:true}")
86
+
87
+ result = @connection.post("/foo", "body")
88
+ expect(result.body).to eq("{result:true}")
89
+ end
90
+
91
+ it "should pass a DELETE request through to Faraday" do
92
+ stub_request(:delete, "www.example.com/foo").
93
+ with(:headers => @default_headers).
94
+ to_return(body: "{result:true}")
95
+
96
+ result = @connection.delete("/foo")
97
+ expect(result.body).to eq("{result:true}")
98
+ end
99
+ end
100
+
101
+ context 'with api auth signing requests' do
102
+ before(:each) do
103
+ # Need to still call this to load the api_auth library so tests work
104
+ Flexirest::Base.api_auth_credentials('id123', 'secret123')
105
+
106
+ @options = {
107
+ :api_auth => {
108
+ :api_auth_access_id => 'id123',
109
+ :api_auth_secret_key => 'secret123'
110
+ }
111
+ }
112
+
113
+ @default_headers = {'Date' => 'Sat, 14 Mar 2015 15:13:24 GMT'}
114
+
115
+ Flexirest::Base.faraday_config do |faraday|
116
+ faraday.adapter Flexirest::Base.adapter
117
+ faraday.headers.update(@default_headers)
118
+ end
119
+ @connection.reconnect
120
+ end
121
+
122
+ it 'should have an Authorization header' do
123
+ stub_request(:get, "www.example.com/foo")
124
+ .with(:headers => @default_headers)
125
+ .to_return(body: "{result:true}")
126
+ result = @connection.get("/foo", @options)
127
+ expect(result.env.request_headers['Authorization']).to eq("APIAuth id123:PMWBThkB8vKbvUccHvoqu9G3eVk=")
128
+ end
129
+
130
+ it 'should have an Content-MD5 header' do
131
+ stub_request(:put, "www.example.com/foo").
132
+ with(body: "body", :headers => @default_headers).
133
+ to_return(body: "{result:true}")
134
+
135
+ result = @connection.put("/foo", "body", @options)
136
+ expect(result.env.request_headers['Content-MD5']).to eq("hBotaJrYa9FhFEdFPCLG/A==")
137
+ end
138
+ end
139
+
140
+ it "should retry once in the event of a connection failed" do
141
+ stub_request(:get, "www.example.com/foo").to_raise(Faraday::Error::ConnectionFailed.new("Foo"))
142
+ expect { @connection.get("/foo") }.to raise_error(Flexirest::ConnectionFailedException)
143
+ end
144
+
145
+ it "should raise an exception on timeout" do
146
+ stub_request(:get, "www.example.com/foo").to_timeout
147
+ expect { @connection.get("/foo") }.to raise_error(Flexirest::TimeoutException)
148
+ end
149
+
150
+ it "should raise an exception on timeout" do
151
+ stub_request(:get, "www.example.com/foo").to_timeout
152
+ begin
153
+ @connection.get("foo")
154
+ fail
155
+ rescue Flexirest::TimeoutException => timeout
156
+ expect(timeout.message).to eq("Timed out getting http://www.example.com/foo")
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flexirest::HeadersList do
4
+ let(:headers_list) { Flexirest::HeadersList.new }
5
+
6
+ it "should remember stored headers" do
7
+ url = "http://www.google.com"
8
+ headers_list["X-My-Header"] = url
9
+ expect(headers_list["X-My-Header"]).to eq(url)
10
+ end
11
+
12
+ it "should remember overwrite normal headers" do
13
+ url = "http://www.google.com"
14
+ headers_list["X-My-Header"] = "SHOULD NEVER BE SEEN"
15
+ headers_list["X-My-Header"] = url
16
+ expect(headers_list["X-My-Header"]).to eq(url)
17
+ end
18
+
19
+ it "should append to specific headers, such as Set-cookie" do
20
+ headers_list["Set-Cookie"] = "first_value"
21
+ headers_list["Set-Cookie"] = "second_value"
22
+ expect(headers_list["Set-Cookie"]).to eq(%w{first_value second_value})
23
+ end
24
+
25
+ it "should not be case sensitive on header names when setting headers" do
26
+ url = "http://www.google.com"
27
+ headers_list["X-My-Header"] = "SHOULD NEVER BE SEEN"
28
+ headers_list["X-MY-HEADER"] = url
29
+ expect(headers_list["X-My-Header"]).to eq(url)
30
+ end
31
+
32
+ it "should not be case sensitive on header names when getting headers" do
33
+ url = "http://www.google.com"
34
+ headers_list["X-My-Header"] = url
35
+ expect(headers_list["X-MY-HEADER"]).to eq(url)
36
+ end
37
+
38
+ it "should allow iterating over headers set, by default with array items returned whole" do
39
+ headers_list["X-My-Header"] = "http://www.google.com"
40
+ headers_list["Set-Cookie"] = "first_value"
41
+ headers_list["SET-COOKIE"] = "second_value"
42
+ values = []
43
+ headers_list.each do |name, value|
44
+ values << "#{name}=#{value.to_s}"
45
+ end
46
+ expect(values.size).to eq(2)
47
+ expect(values).to eq(["X-My-Header=http://www.google.com", "Set-Cookie=[\"first_value\", \"second_value\"]"])
48
+ end
49
+
50
+ it "should allow iterating over headers set splitting array headers in to individual ones" do
51
+ headers_list["X-My-Header"] = "http://www.google.com"
52
+ headers_list["Set-Cookie"] = "first_value"
53
+ headers_list["SET-COOKIE"] = "second_value"
54
+ values = []
55
+ headers_list.each(true) do |name, value|
56
+ values << "#{name}=#{value.to_s}"
57
+ end
58
+ expect(values.size).to eq(3)
59
+ expect(values).to eq(["X-My-Header=http://www.google.com", "Set-Cookie=first_value", "Set-Cookie=second_value"])
60
+ end
61
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ class InstrumentationExampleClient < Flexirest::Base
4
+ base_url "http://www.example.com"
5
+ get :fake, "/fake", fake:"{\"result\":true, \"list\":[1,2,3,{\"test\":true}], \"child\":{\"grandchild\":{\"test\":true}}}"
6
+ get :real, "/real"
7
+ end
8
+
9
+ describe Flexirest::Instrumentation do
10
+ it "should save a load hook to include the instrumentation" do
11
+ hook_tester = double("HookTester")
12
+ expect(hook_tester).to receive(:include).with(Flexirest::ControllerInstrumentation)
13
+ ActiveSupport.run_load_hooks(:action_controller, hook_tester)
14
+ end
15
+
16
+ it "should call ActiveSupport::Notifications.instrument when making any request" do
17
+ expect(ActiveSupport::Notifications).to receive(:instrument).with("request_call.flexirest", {:name=>"InstrumentationExampleClient#fake"})
18
+ InstrumentationExampleClient.fake
19
+ end
20
+
21
+ it "should call ActiveSupport::Notifications#request_call when making any request" do
22
+ expect_any_instance_of(Flexirest::Instrumentation).to receive(:request_call).with(an_instance_of(ActiveSupport::Notifications::Event))
23
+ InstrumentationExampleClient.fake
24
+ end
25
+
26
+
27
+ it "should log time spent in each API call" do
28
+ expect_any_instance_of(Flexirest::Connection).
29
+ to receive(:get).
30
+ with("/real", an_instance_of(Hash)).
31
+ and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"first_name\":\"John\", \"id\":1234}", response_headers:{}, status:200)))
32
+ expect(Flexirest::Logger).to receive(:debug).with(/Flexirest.*ms\)/)
33
+ expect(Flexirest::Logger).to receive(:debug).at_least(:once).with(any_args)
34
+ InstrumentationExampleClient.real
35
+ end
36
+
37
+
38
+ it "should report the total time spent" do
39
+ # Create a couple of classes to fake being part of ActionController (that would normally call this method)
40
+ class InstrumentationTimeSpentExampleClientParent
41
+ def append_info_to_payload(payload) ; {} ; end
42
+ def self.log_process_action(payload) ; [] ; end
43
+ end
44
+
45
+ class InstrumentationTimeSpentExampleClient < InstrumentationTimeSpentExampleClientParent
46
+ include Flexirest::ControllerInstrumentation
47
+
48
+ def test
49
+ payload = {}
50
+ append_info_to_payload(payload)
51
+ self.class.log_process_action(payload)
52
+ end
53
+ end
54
+
55
+ messages = InstrumentationTimeSpentExampleClient.new.test
56
+ expect(messages.first).to match(/Flexirest.*ms.*call/)
57
+ end
58
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ class MonthExample < Flexirest::Base
4
+ base_url "http://www.example.com"
5
+
6
+ get :find, "/month/:id", fake:"{\"name\":\"january\"}"
7
+ end
8
+
9
+ class YearExample < Flexirest::Base
10
+ base_url "http://www.example.com"
11
+
12
+ get :find, "/year/:id", lazy: { months: MonthExample }, fake: "{\"months\": [\"http://www.example.com/months/1\"] }"
13
+ end
14
+
15
+ describe Flexirest::LazyAssociationLoader do
16
+ let(:url1) { "http://www.example.com/some/url" }
17
+ let(:url2) { "http://www.example.com/some/other" }
18
+ let(:calling_object) { o = double("Object").as_null_object }
19
+ let(:request) { Flexirest::Request.new({:method => :get, url:"http://api.example.com/v1/foo"}, calling_object) }
20
+
21
+ it "should raise an exception if you initialize it with a value that is not a string, hash or array" do
22
+ expect do
23
+ Flexirest::LazyAssociationLoader.new(:person, OpenStruct.new, nil)
24
+ end.to raise_error(Flexirest::InvalidLazyAssociationContentException)
25
+ end
26
+
27
+ it "should store a URL passed as a string to the new object during creation" do
28
+ loader = Flexirest::LazyAssociationLoader.new(:person, url1, nil)
29
+ expect(loader.instance_variable_get(:@url)).to eq(url1)
30
+ end
31
+
32
+ it "should store a URL from a hash passed to the new object during creation" do
33
+ loader = Flexirest::LazyAssociationLoader.new(:person, {"url" => url1}, nil)
34
+ expect(loader.instance_variable_get(:@url)).to eq(url1)
35
+ end
36
+
37
+ it "should store a list of URLs from an array passed to the new object during creation" do
38
+ loader = Flexirest::LazyAssociationLoader.new(:person, [url1, url2], nil)
39
+ array = loader.instance_variable_get(:@subloaders)
40
+ expect(array[0].instance_variable_get(:@url)).to eq(url1)
41
+ expect(array[1].instance_variable_get(:@url)).to eq(url2)
42
+ expect(array[2]).to be_nil
43
+ end
44
+
45
+ it "should store a hash of URLs from a hash passed to the new object during creation" do
46
+ loader = Flexirest::LazyAssociationLoader.new(:person, {"main" => url1, "thumb" => url2}, request)
47
+ expect(loader.main.instance_variable_get(:@url)).to eq(url1)
48
+ expect(loader.thumb.instance_variable_get(:@url)).to eq(url2)
49
+ expect(loader.size).to eq(2)
50
+ end
51
+
52
+ it "should still be able to iterate over a hash of URLs from a hash passed to the new object during creation" do
53
+ loader = Flexirest::LazyAssociationLoader.new(:person, {"main" => url1, "thumb" => url2}, request)
54
+ output = []
55
+ loader.each do |k, v|
56
+ output << v.instance_variable_get(:@url)
57
+ end
58
+ expect(output.size).to eq(2)
59
+ expect(output[0]).to eq(url1)
60
+ expect(output[1]).to eq(url2)
61
+ expect(output[2]).to be_nil
62
+ end
63
+
64
+ it "should be able to list the keys from a hash passed to the new object during creation" do
65
+ loader = Flexirest::LazyAssociationLoader.new(:person, {"main" => url1, "thumb" => url2}, request)
66
+ expect(loader.keys[0]).to eq(:main)
67
+ expect(loader.keys[1]).to eq(:thumb)
68
+ expect(loader.keys.size).to eq(2)
69
+ end
70
+
71
+ it "should report the size of a list of stored URLs" do
72
+ loader = Flexirest::LazyAssociationLoader.new(:person, [url1, url2], nil)
73
+ expect(loader.size).to eq(2)
74
+ end
75
+
76
+ it "should respond to each and iterate through the list of stored URLs" do
77
+ loader = Flexirest::LazyAssociationLoader.new(:person, [url1, url2], nil)
78
+ output = []
79
+ loader.each do |o|
80
+ output << o.instance_variable_get(:@url)
81
+ end
82
+ expect(output.size).to eq(2)
83
+ expect(output[0]).to eq(url1)
84
+ expect(output[1]).to eq(url2)
85
+ expect(output[2]).to be_nil
86
+ end
87
+
88
+ it "should return a LazyAssociationLoader for each stored URL in a list" do
89
+ loader = Flexirest::LazyAssociationLoader.new(:person, [url1, url2], nil)
90
+ output = []
91
+ loader.each do |o|
92
+ expect(o).to be_an_instance_of(Flexirest::LazyAssociationLoader)
93
+ end
94
+ end
95
+
96
+ it "should make the request for a URL if it's accessed" do
97
+ method_data = {options:{url:"foo"}}
98
+ request = double("Request").as_null_object
99
+ allow(request).to receive(:method).and_return(method_data)
100
+ expect(request).to receive(:object).with(any_args).and_return(Array.new)
101
+ expect(request).to receive(:call).with(any_args).and_return("")
102
+ expect(Flexirest::Request).to receive(:new).with(any_args).and_return(request)
103
+ loader = Flexirest::LazyAssociationLoader.new(:person, url1, request)
104
+ loader.length
105
+ end
106
+
107
+ it "should proxy methods to the underlying object if the request has been made" do
108
+ loader = Flexirest::LazyAssociationLoader.new(:person, url1, request)
109
+ object = double("Object")
110
+ expect(object).to receive(:length).and_return(1)
111
+ loader.instance_variable_set(:@object, object)
112
+ expect(loader.length).to eq(1)
113
+ end
114
+
115
+ it "should be able to iterate underlying object if it's an array" do
116
+ loader = Flexirest::LazyAssociationLoader.new(:person, url1, request)
117
+ expect_any_instance_of(Flexirest::Request).to receive(:call).with(any_args).and_return([1,2,3])
118
+ test = []
119
+ loader.each do |item|
120
+ test << item
121
+ end
122
+ expect(test).to eq([1,2,3])
123
+ end
124
+
125
+ it "should be able to return the size of the underlying object if it's an array" do
126
+ loader = Flexirest::LazyAssociationLoader.new(:person, url1, request)
127
+ expect_any_instance_of(Flexirest::Request).to receive(:call).with(any_args).and_return([1,2,3])
128
+ expect(loader.size).to eq(3)
129
+ end
130
+
131
+ it "should use the class specified in the 'lazy' declaration to parse the response rather than the class of the object the lazy loader is attached to" do
132
+ association = YearExample.find(1)
133
+ expect(association.months.instance_variable_get('@request').instance_variable_get('@object').class).to eq(MonthExample)
134
+ end
135
+ end