px-service-client 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +19 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +140 -0
  10. data/Rakefile +9 -0
  11. data/lib/px/service/client/base.rb +41 -0
  12. data/lib/px/service/client/caching/cache_entry.rb +95 -0
  13. data/lib/px/service/client/caching/log_subscriber.rb +23 -0
  14. data/lib/px/service/client/caching/railtie.rb +11 -0
  15. data/lib/px/service/client/caching.rb +112 -0
  16. data/lib/px/service/client/circuit_breaker.rb +47 -0
  17. data/lib/px/service/client/future.rb +91 -0
  18. data/lib/px/service/client/list_response.rb +80 -0
  19. data/lib/px/service/client/multiplexer.rb +34 -0
  20. data/lib/px/service/client/retriable_response_future.rb +98 -0
  21. data/lib/px/service/client/version.rb +7 -0
  22. data/lib/px/service/client.rb +19 -0
  23. data/lib/px/service/errors.rb +28 -0
  24. data/lib/px-service-client.rb +1 -0
  25. data/px-service-client.gemspec +35 -0
  26. data/spec/px/service/client/base_spec.rb +49 -0
  27. data/spec/px/service/client/caching/caching_spec.rb +209 -0
  28. data/spec/px/service/client/circuit_breaker_spec.rb +113 -0
  29. data/spec/px/service/client/future_spec.rb +182 -0
  30. data/spec/px/service/client/list_response_spec.rb +118 -0
  31. data/spec/px/service/client/multiplexer_spec.rb +63 -0
  32. data/spec/px/service/client/retriable_response_future_spec.rb +99 -0
  33. data/spec/spec_helper.rb +25 -0
  34. data/spec/vcr/Px_Service_Client_Multiplexer/with_multiple_requests/when_the_requests_depend_on_each_other/runs_the_requests.yml +91 -0
  35. data/spec/vcr/Px_Service_Client_Multiplexer/with_multiple_requests/when_the_requests_don_t_depend_on_each_other/runs_the_requests.yml +91 -0
  36. data/spec/vcr/Px_Service_Client_Multiplexer/with_one_request/returns_a_ResponseFuture.yml +47 -0
  37. data/spec/vcr/Px_Service_Client_Multiplexer/with_one_request/runs_the_requests.yml +47 -0
  38. metadata +288 -0
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ describe Px::Service::Client::Future do
4
+ subject { Px::Service::Client::Future.new }
5
+ let(:value) { "value" }
6
+
7
+ describe '#complete' do
8
+ it "calls any pending methods on the response" do
9
+ expect(value).to receive(:size)
10
+ called = false
11
+ subject
12
+
13
+ Fiber.new do
14
+ subject.size
15
+ called = true
16
+ end.resume
17
+
18
+ Fiber.new do
19
+ subject.complete(value)
20
+ end.resume
21
+
22
+ expect(called).to eq(true)
23
+ end
24
+ end
25
+
26
+ describe '#completed?' do
27
+ context "when the future is completed" do
28
+ it "returns true" do
29
+ Fiber.new do
30
+ subject.complete(value)
31
+ end.resume
32
+
33
+ expect(subject.completed?).to eq(true)
34
+ end
35
+ end
36
+
37
+ context "when the future is not completed" do
38
+ it "returns false" do
39
+ expect(subject.completed?).to eq(false)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#value' do
45
+ context "when the future is not complete" do
46
+ it "does not call the method on the value" do
47
+ called = false
48
+ Fiber.new do
49
+ subject.size
50
+ called = true
51
+ end.resume
52
+
53
+ expect(called).to eq(false)
54
+ end
55
+ end
56
+
57
+ context "when the future is already complete" do
58
+ it "returns the value" do
59
+ subject.complete(value)
60
+ expect(subject.value).to eq(value)
61
+ end
62
+ end
63
+
64
+ context "when the value is an exception" do
65
+ it "returns the exception" do
66
+ Fiber.new do
67
+ expect(subject.value).to be_a(ArgumentError)
68
+ end.resume
69
+
70
+ Fiber.new do
71
+ subject.complete(ArgumentError.new("Error"))
72
+ end.resume
73
+ end
74
+ end
75
+
76
+ context "when the method returns a value" do
77
+ it "returns the value" do
78
+ Fiber.new do
79
+ expect(subject.value).to eq(value)
80
+ end.resume
81
+
82
+ Fiber.new do
83
+ subject.complete(value)
84
+ end.resume
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#method_missing' do
90
+ context "when the future is already complete" do
91
+ context "when the method raised an exception" do
92
+ before :each do
93
+ subject.complete(nil)
94
+ end
95
+
96
+ it "raises the exception" do
97
+ Fiber.new do
98
+ expect {
99
+ subject.size
100
+ }.to raise_error(NoMethodError)
101
+ end.resume
102
+ end
103
+ end
104
+
105
+ context "when the method doesn't exist" do
106
+ before :each do
107
+ subject.complete("I am a string")
108
+ end
109
+
110
+ it "raises an exception" do
111
+ Fiber.new do
112
+ expect {
113
+ subject.not_exist
114
+ }.to raise_error(NoMethodError)
115
+ end.resume
116
+ end
117
+ end
118
+
119
+ context "when the method returns a value" do
120
+ before :each do
121
+ subject.complete(value)
122
+ end
123
+
124
+ it "returns the value" do
125
+ Fiber.new do
126
+ expect(subject.size).to eq(value.size)
127
+ end.resume
128
+ end
129
+ end
130
+ end
131
+
132
+ context "when the future is not complete" do
133
+ it "does not call the method on the value" do
134
+ expect(value).not_to receive(:size)
135
+
136
+ Fiber.new do
137
+ subject.size
138
+ end.resume
139
+ end
140
+
141
+ context "when the method raised an exception" do
142
+ it "raises the exception" do
143
+ Fiber.new do
144
+ expect {
145
+ subject.size
146
+ }.to raise_error(NoMethodError)
147
+ end.resume
148
+
149
+ Fiber.new do
150
+ subject.complete(nil)
151
+ end.resume
152
+ end
153
+ end
154
+
155
+ context "when the method doesn't exist on the eventual value" do
156
+ it "raises an exception" do
157
+ Fiber.new do
158
+ expect {
159
+ subject.not_exist
160
+ }.to raise_error(NoMethodError)
161
+ end.resume
162
+
163
+ Fiber.new do
164
+ subject.complete("I am a string")
165
+ end.resume
166
+ end
167
+ end
168
+
169
+ context "when the method returns a value" do
170
+ it "returns the value" do
171
+ Fiber.new do
172
+ expect(subject.size).to eq(value.size)
173
+ end.resume
174
+
175
+ Fiber.new do
176
+ subject.complete(value)
177
+ end.resume
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Px::Service::Client::ListResponse do
4
+ let (:response) do
5
+ {
6
+ "current_page" => 5,
7
+ "total_pages" => 1,
8
+ "total_items" => 3,
9
+ "took" => 123,
10
+ "results" => [
11
+ { "id" => 1, "type" => "photo", "score" => 1},
12
+ { "id" => 2, "type" => "photo", "score" => 1},
13
+ { "id" => 3, "type" => "photo", "score" => 1},
14
+ ],
15
+ }
16
+ end
17
+
18
+ let (:empty_response) do
19
+ {
20
+ "current_page" => 1,
21
+ "total_pages" => 1,
22
+ "total_items" => 0,
23
+ "took" => 123,
24
+ "results" => [],
25
+ }
26
+ end
27
+
28
+ subject { Px::Service::Client::ListResponse.new(20, response, "results", name: "value") }
29
+
30
+ describe '#results' do
31
+ it "results the results" do
32
+ expect(subject.results).to eq(response["results"])
33
+ end
34
+ end
35
+
36
+
37
+ describe '#per_page' do
38
+ it "returns the page size" do
39
+ expect(subject.per_page).to be(20)
40
+ end
41
+ end
42
+
43
+ describe '#current_page' do
44
+ it "returns the current page" do
45
+ expect(subject.current_page).to be(5)
46
+ end
47
+ end
48
+
49
+ describe '#total_entries' do
50
+ it "returns the total number of entries" do
51
+ expect(subject.total_entries).to be(3)
52
+ end
53
+ end
54
+
55
+ describe '#total_pages' do
56
+ it "returns the total number of pages" do
57
+ expect(subject.total_pages).to be(1)
58
+ end
59
+ end
60
+
61
+ describe '#offset' do
62
+ it "returns the offset" do
63
+ expect(subject.offset).to be(80)
64
+ end
65
+ end
66
+
67
+ describe '#empty?' do
68
+ context "when there are results" do
69
+ it "returns false" do
70
+ expect(subject.empty?).to be_falsey
71
+ end
72
+ end
73
+
74
+ context "when there are no results" do
75
+ subject { Px::Service::Client::ListResponse.new(20, empty_response, "results", name: "value") }
76
+
77
+ it "returns true" do
78
+ expect(subject.empty?).to be_truthy
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#==' do
84
+ let(:other_search) { Px::Service::Client::ListResponse.new(20, response, "results", name: "value") }
85
+
86
+ context "when compared with another response" do
87
+ it "returns true" do
88
+ expect(subject == other_search).to be_truthy
89
+ end
90
+ end
91
+
92
+ context "when compared with an array" do
93
+ it "returns true" do
94
+ expect(subject == other_search.to_a).to be_truthy
95
+ end
96
+ end
97
+
98
+ context "when compared with nil" do
99
+ it "returns false" do
100
+ expect(subject == nil).to be_falsey
101
+ end
102
+ end
103
+
104
+ context "when compared with something else" do
105
+ it "returns false" do
106
+ expect(subject == "stuff").to be_falsey
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#each' do
112
+ it "iterates over the list of results" do
113
+ subject.each do |result|
114
+ expect(result).to be_a(Hash)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Px::Service::Client::Multiplexer do
4
+ let(:client) { Px::Service::Client::Base.send(:new) }
5
+
6
+ context '.new' do
7
+ it 'should work with no argments' do
8
+ expect {
9
+ Px::Service::Client::Multiplexer.new
10
+ }.not_to raise_error
11
+ end
12
+ end
13
+
14
+ context 'with one request', vcr: true do
15
+ let(:req1) { client.send(:make_request, :get, 'http://localhost:3000/status') }
16
+
17
+ it "returns a ResponseFuture" do
18
+ subject.context do
19
+ resp1 = subject.do(req1)
20
+
21
+ expect(resp1).to be_a(Px::Service::Client::RetriableResponseFuture)
22
+ end.run
23
+ end
24
+
25
+ it "runs the requests" do
26
+ subject.context do
27
+ resp1 = subject.do(req1)
28
+
29
+ expect(resp1.body).to eq("OK")
30
+ end.run
31
+ end
32
+ end
33
+
34
+ context 'with multiple requests', vcr: true do
35
+ let(:req1) { client.send(:make_request, :get, 'http://localhost:3000/status') }
36
+ let(:req2) { client.send(:make_request, :get, 'http://localhost:3000/status') }
37
+
38
+ context "when the requests don't depend on each other" do
39
+ it "runs the requests" do
40
+ subject.context do
41
+ resp1 = subject.do(req1)
42
+ resp2 = subject.do(req2)
43
+
44
+ expect(resp2.body).to eq("OK")
45
+ expect(resp1.body).to eq("OK")
46
+ end.run
47
+ end
48
+ end
49
+
50
+ context "when the requests depend on each other" do
51
+ it "runs the requests" do
52
+ subject.context do
53
+ resp1 = subject.do(req1)
54
+ client.send(:make_request, :get, "http://localhost:3000/status?#{resp1.body}")
55
+ resp2 = subject.do(req2)
56
+
57
+ expect(resp2.body).to eq("OK")
58
+ expect(resp1.body).to eq("OK")
59
+ end.run
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe Px::Service::Client::RetriableResponseFuture do
4
+ let(:request) { Typhoeus::Request.new('http://localhost:3000/status') }
5
+ let(:response) do
6
+ Typhoeus::Response.new(
7
+ code: 200,
8
+ body: { status: 200, message: "Success"}.to_json,
9
+ headers: { "Content-Type" => "application/json"} )
10
+ end
11
+ let(:hydra) { Typhoeus::Hydra.new }
12
+ subject { Px::Service::Client::RetriableResponseFuture.new(request) }
13
+
14
+ before :each do
15
+ Typhoeus.stub(/status/).and_return(response)
16
+ end
17
+
18
+ describe '#hydra=' do
19
+ it "queues the request on the hydra" do
20
+ expect(hydra).to receive(:queue).with(request)
21
+
22
+ subject.hydra = hydra
23
+ end
24
+ end
25
+
26
+ describe '#method_missing' do
27
+ context "when the request is still in progress" do
28
+ it "does not call the method on the response" do
29
+ Fiber.new do
30
+ expect(response).not_to receive(:body)
31
+
32
+ subject.hydra = hydra
33
+
34
+ subject.body
35
+ end.resume
36
+ end
37
+ end
38
+
39
+ context "when the request status is an error" do
40
+ let(:response) do
41
+ Typhoeus::Response.new(
42
+ code: 500,
43
+ body: { status: 500, error: "Failed"}.to_json,
44
+ headers: { "Content-Type" => "application/json"} )
45
+ end
46
+
47
+ it "completes the future only once" do
48
+ Fiber.new do
49
+ expect {
50
+ subject.total_time
51
+ }.to raise_error(Px::Service::ServiceError, "Failed")
52
+ end.resume
53
+
54
+ Fiber.new do
55
+ subject.hydra = hydra
56
+ hydra.run
57
+ end.resume
58
+ end
59
+
60
+ it "retries the request" do
61
+ f = Px::Service::Client::RetriableResponseFuture.new(retries: 3)
62
+
63
+ Fiber.new do
64
+ expect {
65
+ f.response_code
66
+ }.to raise_error(Px::Service::ServiceError, "Failed")
67
+ end.resume
68
+
69
+ Fiber.new do
70
+ expect(hydra).to receive(:queue).with(request).exactly(4).times.and_call_original
71
+ f.request = request
72
+
73
+ f.hydra = hydra
74
+ hydra.run
75
+ end.resume
76
+ end
77
+ end
78
+
79
+ context "when the request completes" do
80
+ it "calls any pending methods on the response" do
81
+ expect(response).to receive(:total_time)
82
+ called = false
83
+
84
+ Fiber.new do
85
+ subject.total_time
86
+ called = true
87
+ end.resume
88
+
89
+ Fiber.new do
90
+ subject.hydra = hydra
91
+ hydra.run
92
+ end.resume
93
+
94
+ expect(called).to eq(true)
95
+ end
96
+ end
97
+ end
98
+
99
+ end
@@ -0,0 +1,25 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'px/service/client'
5
+ require 'timecop'
6
+ require 'vcr'
7
+
8
+ ##
9
+ # VCR config
10
+ VCR.configure do |c|
11
+ c.cassette_library_dir = 'spec/vcr'
12
+ c.hook_into :typhoeus, :webmock
13
+ c.allow_http_connections_when_no_cassette = true
14
+ c.configure_rspec_metadata!
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.before(:each) do
19
+ Typhoeus::Expectation.clear
20
+ end
21
+
22
+ config.after(:each) do
23
+ Timecop.return
24
+ end
25
+ end
@@ -0,0 +1,91 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:3000/status
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Typhoeus - https://github.com/typhoeus/typhoeus
12
+ response:
13
+ status:
14
+ code: 200
15
+ message: OK
16
+ headers:
17
+ X-Frame-Options:
18
+ - SAMEORIGIN
19
+ Content-Type:
20
+ - text/html; charset=utf-8
21
+ X-UA-Compatible:
22
+ - IE=Edge
23
+ ETag:
24
+ - "\"e0aa021e21dddbd6d8cecec71e9cf564\""
25
+ Cache-Control:
26
+ - max-age=0, private, must-revalidate
27
+ Set-Cookie:
28
+ - landing_page=%2Fstatus; path=/
29
+ - referrer_type=other; path=/
30
+ - _hpx1=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTNkODFkMDk4MzEyZDExYWI0MTE1YzAwNWYzZTAxM2ZiBjsAVEkiCWhvc3QGOwBGIg5sb2NhbGhvc3Q%3D--85ef4ecf15347d5b26bf5a0c3adae2ff744341aa;
31
+ path=/; HttpOnly
32
+ X-Request-Id:
33
+ - 7dbb973d1e14238a8749e5febcafa9f9
34
+ X-Runtime:
35
+ - '0.017194'
36
+ Connection:
37
+ - close
38
+ Server:
39
+ - thin 1.5.0 codename Knife
40
+ body:
41
+ encoding: UTF-8
42
+ string: OK
43
+ http_version: '1.1'
44
+ adapter_metadata:
45
+ effective_url: http://localhost:3000/status
46
+ recorded_at: Tue, 11 Nov 2014 20:22:51 GMT
47
+ - request:
48
+ method: get
49
+ uri: http://localhost:3000/status
50
+ body:
51
+ encoding: US-ASCII
52
+ string: ''
53
+ headers:
54
+ User-Agent:
55
+ - Typhoeus - https://github.com/typhoeus/typhoeus
56
+ response:
57
+ status:
58
+ code: 200
59
+ message: OK
60
+ headers:
61
+ X-Frame-Options:
62
+ - SAMEORIGIN
63
+ Content-Type:
64
+ - text/html; charset=utf-8
65
+ X-UA-Compatible:
66
+ - IE=Edge
67
+ ETag:
68
+ - "\"e0aa021e21dddbd6d8cecec71e9cf564\""
69
+ Cache-Control:
70
+ - max-age=0, private, must-revalidate
71
+ Set-Cookie:
72
+ - landing_page=%2Fstatus; path=/
73
+ - referrer_type=other; path=/
74
+ - _hpx1=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTA1ODdiZDhkOWE2MTk4OWYxMjJhZWM2MjdkZDRkMGNhBjsAVEkiCWhvc3QGOwBGIg5sb2NhbGhvc3Q%3D--61cd8e3e21637857a0eb745049adb6d9e172c1fc;
75
+ path=/; HttpOnly
76
+ X-Request-Id:
77
+ - c8ce1b4570fe232431ca9716a6ee90ea
78
+ X-Runtime:
79
+ - '0.018474'
80
+ Connection:
81
+ - close
82
+ Server:
83
+ - thin 1.5.0 codename Knife
84
+ body:
85
+ encoding: UTF-8
86
+ string: OK
87
+ http_version: '1.1'
88
+ adapter_metadata:
89
+ effective_url: http://localhost:3000/status
90
+ recorded_at: Tue, 11 Nov 2014 20:22:51 GMT
91
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,91 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:3000/status
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Typhoeus - https://github.com/typhoeus/typhoeus
12
+ response:
13
+ status:
14
+ code: 200
15
+ message: OK
16
+ headers:
17
+ X-Frame-Options:
18
+ - SAMEORIGIN
19
+ Content-Type:
20
+ - text/html; charset=utf-8
21
+ X-UA-Compatible:
22
+ - IE=Edge
23
+ ETag:
24
+ - "\"e0aa021e21dddbd6d8cecec71e9cf564\""
25
+ Cache-Control:
26
+ - max-age=0, private, must-revalidate
27
+ Set-Cookie:
28
+ - landing_page=%2Fstatus; path=/
29
+ - referrer_type=other; path=/
30
+ - _hpx1=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTRiMWVlZjEyYTA3ZmQ2NTA5NzMzNThlZmRjODUwOTJlBjsAVEkiCWhvc3QGOwBGIg5sb2NhbGhvc3Q%3D--70cae34deed03c594c3ab738150b3d7e20b32fb2;
31
+ path=/; HttpOnly
32
+ X-Request-Id:
33
+ - 7dae1a92439fed487abb36164364741a
34
+ X-Runtime:
35
+ - '0.017075'
36
+ Connection:
37
+ - close
38
+ Server:
39
+ - thin 1.5.0 codename Knife
40
+ body:
41
+ encoding: UTF-8
42
+ string: OK
43
+ http_version: '1.1'
44
+ adapter_metadata:
45
+ effective_url: http://localhost:3000/status
46
+ recorded_at: Tue, 11 Nov 2014 20:22:51 GMT
47
+ - request:
48
+ method: get
49
+ uri: http://localhost:3000/status
50
+ body:
51
+ encoding: US-ASCII
52
+ string: ''
53
+ headers:
54
+ User-Agent:
55
+ - Typhoeus - https://github.com/typhoeus/typhoeus
56
+ response:
57
+ status:
58
+ code: 200
59
+ message: OK
60
+ headers:
61
+ X-Frame-Options:
62
+ - SAMEORIGIN
63
+ Content-Type:
64
+ - text/html; charset=utf-8
65
+ X-UA-Compatible:
66
+ - IE=Edge
67
+ ETag:
68
+ - "\"e0aa021e21dddbd6d8cecec71e9cf564\""
69
+ Cache-Control:
70
+ - max-age=0, private, must-revalidate
71
+ Set-Cookie:
72
+ - landing_page=%2Fstatus; path=/
73
+ - referrer_type=other; path=/
74
+ - _hpx1=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTgyMjA5OGEzN2Y3YTczN2ViMzk0ODRjOTk4MTEyNDIzBjsAVEkiCWhvc3QGOwBGIg5sb2NhbGhvc3Q%3D--972313d92247acf7eff13cc669f2c8b4c481e92c;
75
+ path=/; HttpOnly
76
+ X-Request-Id:
77
+ - 8ac3f0a3228f949c5599419f2996d8ca
78
+ X-Runtime:
79
+ - '0.016529'
80
+ Connection:
81
+ - close
82
+ Server:
83
+ - thin 1.5.0 codename Knife
84
+ body:
85
+ encoding: UTF-8
86
+ string: OK
87
+ http_version: '1.1'
88
+ adapter_metadata:
89
+ effective_url: http://localhost:3000/status
90
+ recorded_at: Tue, 11 Nov 2014 20:22:51 GMT
91
+ recorded_with: VCR 2.9.3