px-service-client 1.0.1

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 (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