http_api_client 0.1.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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +143 -0
- data/Rakefile +1 -0
- data/http_api_client.gemspec +33 -0
- data/lib/http_api_client/client.rb +159 -0
- data/lib/http_api_client/config.rb +58 -0
- data/lib/http_api_client/connection_factory.rb +58 -0
- data/lib/http_api_client/errors.rb +90 -0
- data/lib/http_api_client/rails_params_encoder.rb +9 -0
- data/lib/http_api_client/timed_result.rb +24 -0
- data/lib/http_api_client/version.rb +3 -0
- data/lib/http_api_client.rb +67 -0
- data/spec/config/http_api_clients.yml +6 -0
- data/spec/config/http_api_clients_with_basic_auth.yml +8 -0
- data/spec/config/http_api_clients_with_request_id.yml +7 -0
- data/spec/config/http_api_clients_with_self_signed_cert.yml +7 -0
- data/spec/config/http_api_clients_with_ssl.yml +6 -0
- data/spec/http_api_client_spec.rb +58 -0
- data/spec/http_client/client_spec.rb +157 -0
- data/spec/http_client/config_spec.rb +32 -0
- data/spec/http_client/connection_factory_spec.rb +99 -0
- data/spec/http_client/errors_spec.rb +35 -0
- data/spec/http_client/timed_result_spec.rb +31 -0
- metadata +196 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require "http_api_client/version"
|
2
|
+
require "http_api_client/errors"
|
3
|
+
require "http_api_client/config"
|
4
|
+
require "http_api_client/client"
|
5
|
+
|
6
|
+
module HttpApiClient
|
7
|
+
|
8
|
+
|
9
|
+
def self.env
|
10
|
+
if @env
|
11
|
+
@env
|
12
|
+
elsif rails
|
13
|
+
rails.env
|
14
|
+
else
|
15
|
+
"test"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.env=(env)
|
20
|
+
@env = env
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.logger
|
24
|
+
if rails
|
25
|
+
rails.logger
|
26
|
+
else
|
27
|
+
puts 'Logger not defined, using stub logger that does nothing. Set logger via HttpApiClient.logger = my_logger'
|
28
|
+
StubLogger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.params_encoder
|
33
|
+
if rails
|
34
|
+
RailsParamsEncoder
|
35
|
+
else
|
36
|
+
Faraday::Utils.default_params_encoder
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self.rails
|
43
|
+
Module.const_get('Rails')
|
44
|
+
rescue NameError
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
class StubLogger
|
49
|
+
|
50
|
+
def self.info(message)
|
51
|
+
#Stub implementation
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.debug(message)
|
55
|
+
#Stub implementation
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.warn(message)
|
59
|
+
#Stub implementation
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.error(message)
|
63
|
+
#Stub implementation
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'http_api_client'
|
2
|
+
|
3
|
+
describe HttpApiClient do
|
4
|
+
|
5
|
+
context "without rails" do
|
6
|
+
|
7
|
+
describe ".env" do
|
8
|
+
it 'returns "test" by default' do
|
9
|
+
expect(HttpApiClient.env).to eq 'test'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be set' do
|
13
|
+
HttpApiClient.env = 'staging'
|
14
|
+
expect(HttpApiClient.env).to eq 'staging'
|
15
|
+
HttpApiClient.env = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".logger" do
|
20
|
+
it 'returns a stub logger' do
|
21
|
+
expect(HttpApiClient.logger).to_not be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with rails" do
|
28
|
+
|
29
|
+
before :each do
|
30
|
+
allow(HttpApiClient).to receive(:rails).and_return(StubRails)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".env" do
|
34
|
+
it 'returns the current rails environment' do
|
35
|
+
expect(HttpApiClient.env).to eq StubRails.env
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".logger" do
|
40
|
+
it 'returns the rails logger' do
|
41
|
+
expect(HttpApiClient.logger).to eq StubRails.logger
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class StubRails
|
48
|
+
|
49
|
+
def self.env
|
50
|
+
'custom_env'
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.logger
|
54
|
+
'rails_logger'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'http_api_client/client'
|
4
|
+
|
5
|
+
module HttpApiClient
|
6
|
+
describe Client do
|
7
|
+
|
8
|
+
let(:client) { Client.new(:my_client, 'spec/config/http_api_clients.yml') }
|
9
|
+
|
10
|
+
let(:get_response) { double('get response', body: '{"id": 1}', status: 200) }
|
11
|
+
let(:post_response) { double('post response', body: '{"id": 1}', status: 200) }
|
12
|
+
let(:put_response) { double('put response', body: '{"id": 1}', status: 200) }
|
13
|
+
let(:delete_response) { double('delete response', body: nil, status: 200) }
|
14
|
+
|
15
|
+
let(:connection) { double('connection', get: get_response, post: post_response, put: put_response, delete: delete_response) }
|
16
|
+
|
17
|
+
let(:base_get_headers) do
|
18
|
+
{ 'Accept' => 'application/json' }
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:base_update_headers) do
|
22
|
+
base_get_headers.merge({ 'Accept' => 'application/json', 'Content-Type' => 'application/json' })
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
client.stub(:connection).and_return connection
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#find" do
|
30
|
+
it "calls http connection with get and correct url" do
|
31
|
+
connection.should_receive(:get).with('/test-base-uri/path/1', {}, base_get_headers)
|
32
|
+
client.find('/path', 1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#find_all" do
|
37
|
+
it "calls http connection with correct url without query" do
|
38
|
+
connection.should_receive(:get).with('/test-base-uri/resources', {}, base_get_headers)
|
39
|
+
client.find_all('/resources')
|
40
|
+
end
|
41
|
+
|
42
|
+
it "calls http connection with correct url with query" do
|
43
|
+
connection.should_receive(:get).with("/test-base-uri/resources", { a: 1 }, base_get_headers)
|
44
|
+
client.find_all('/resources', { a: 1 })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#find_nested" do
|
49
|
+
it "calls http connection with get and correct url" do
|
50
|
+
connection.should_receive(:get).with('/test-base-uri/resource/1/resources', {}, base_get_headers)
|
51
|
+
client.find_nested('/resource', 1, '/resources')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#create" do
|
56
|
+
|
57
|
+
context "with auth params" do
|
58
|
+
let(:client) do
|
59
|
+
klass = Class.new(Client) do
|
60
|
+
def auth_params
|
61
|
+
{:key => 'one'}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
klass.new(:my_client, 'spec/config/http_api_clients_with_basic_auth.yml')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "calls http connection with post and correct url and post data" do
|
68
|
+
payload = { text: "hello", :key => 'one' }
|
69
|
+
connection.should_receive(:post).with('/test-base-uri/path', JSON.fast_generate(payload), base_update_headers)
|
70
|
+
client.create('/path', payload)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "calls http connection with post and correct url and post data" do
|
75
|
+
payload = { text: "hello"}
|
76
|
+
connection.should_receive(:post).with('/test-base-uri/path', JSON.fast_generate(payload), base_update_headers)
|
77
|
+
client.create('/path', payload)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#delete" do
|
83
|
+
it "calls http connection with delete and correct url" do
|
84
|
+
connection.should_receive(:delete).with('/test-base-uri/path/1', base_update_headers)
|
85
|
+
client.destroy('/path', 1)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "auth_params" do
|
90
|
+
it "appends auth params to request" do
|
91
|
+
client.stub(:auth_params).and_return({ token: 'abc123' })
|
92
|
+
connection.should_receive(:get).with("/test-base-uri/protected", { token: 'abc123' }, base_get_headers)
|
93
|
+
client.get('/protected')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "response status code handling" do
|
98
|
+
|
99
|
+
context "with a response with a status code in the 200 range" do
|
100
|
+
let(:response) { double('response', :body => '{"id": 1}', status: 200) }
|
101
|
+
|
102
|
+
describe "response" do
|
103
|
+
it "returns expected response body as hash" do
|
104
|
+
response = client.find('/path', 1)
|
105
|
+
expect(response['id']).to eq 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with a response in the 400 range" do
|
111
|
+
let(:get_response) { double('response', :body => '{"id": 1}', status: 404) }
|
112
|
+
|
113
|
+
describe "raising exceptions" do
|
114
|
+
it "raises the expected exception for the status" do
|
115
|
+
lambda { client.find('/path', 1) }.should raise_error(Errors::NotFound)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with a response in the 500 range" do
|
121
|
+
let(:get_response) { double('response', :body => '{"id": 1}', status: 500) }
|
122
|
+
|
123
|
+
describe "raising exceptions" do
|
124
|
+
it "raises the expected exception for the status" do
|
125
|
+
lambda { client.find('/path', 1) }.should raise_error(Errors::InternalServerError)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'without request id tracking configured' do
|
133
|
+
it "does not add the Request-Id header to the request" do
|
134
|
+
connection.should_receive(:get).with('/test-base-uri/path/1', {}, base_get_headers)
|
135
|
+
client.find('/path', 1)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with request id tracking configured" do
|
140
|
+
|
141
|
+
let(:client) { Client.new(:my_client, 'spec/config/http_api_clients_with_request_id.yml') }
|
142
|
+
|
143
|
+
let(:request_id) { 'abc-123' }
|
144
|
+
|
145
|
+
before do
|
146
|
+
Thread.current[:request_id] = request_id
|
147
|
+
end
|
148
|
+
|
149
|
+
it "adds the Request-Id header to the request" do
|
150
|
+
connection.should_receive(:get).with('/test-base-uri/path/1', {}, base_get_headers.merge('X-Request-Id' => request_id))
|
151
|
+
client.find('/path', 1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# require 'spec_helper'
|
2
|
+
require 'http_api_client/config'
|
3
|
+
module HttpApiClient
|
4
|
+
|
5
|
+
describe Config do
|
6
|
+
|
7
|
+
let(:config) { Config.new(config_file) }
|
8
|
+
|
9
|
+
context "with an invalid config file" do
|
10
|
+
|
11
|
+
let(:config_file) { 'invalid' }
|
12
|
+
|
13
|
+
it "raises error" do
|
14
|
+
expect{ config }.to raise_error("Could not load config file: #{config_file}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with a valid config file" do
|
19
|
+
|
20
|
+
let(:config_file) { 'spec/config/http_api_clients.yml' }
|
21
|
+
|
22
|
+
it 'loads the config for the environment' do
|
23
|
+
expect(config.my_client.protocol).to eql 'http'
|
24
|
+
expect(config.my_client.server).to eql 'test-server'
|
25
|
+
expect(config.my_client.port).to eql 80
|
26
|
+
expect(config.my_client.base_uri).to eql 'test-base-uri'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# require 'spec_helper'
|
4
|
+
|
5
|
+
require 'http_api_client/connection_factory'
|
6
|
+
require 'http_api_client/config'
|
7
|
+
|
8
|
+
module HttpApiClient
|
9
|
+
|
10
|
+
describe ConnectionFactory do
|
11
|
+
|
12
|
+
|
13
|
+
let(:config) { Config.new(config_file).send(client_id) }
|
14
|
+
let(:client_id) { 'my_client' }
|
15
|
+
let(:connection_factory) { ConnectionFactory.new(config) }
|
16
|
+
let(:connection) { connection_factory.create }
|
17
|
+
|
18
|
+
context "without ssl" do
|
19
|
+
|
20
|
+
let(:config_file) { 'spec/config/http_api_clients.yml' }
|
21
|
+
|
22
|
+
it "creates a connection based on config" do
|
23
|
+
expect(connection.url_prefix.to_s).to eq 'http://test-server/'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "caches the connection for reuse" do
|
27
|
+
expect(connection).to eq connection
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with ssl" do
|
32
|
+
|
33
|
+
context 'when a self-signed certificate authority is specified' do
|
34
|
+
|
35
|
+
let(:config_file) { 'spec/config/http_api_clients_with_self_signed_cert.yml' }
|
36
|
+
|
37
|
+
it "creates a connection based on config" do
|
38
|
+
expect(connection.url_prefix.to_s).to eq 'https://test-server/'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'sets ca_file path correctly' do
|
42
|
+
expect(connection.ssl.ca_file).to eq '/usr/local/etc/nginx/test-server.crt'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when the machine is OSX' do
|
47
|
+
|
48
|
+
let(:config_file) { 'spec/config/http_api_clients_with_ssl.yml' }
|
49
|
+
|
50
|
+
before { connection_factory.stub(osx?: true) }
|
51
|
+
|
52
|
+
it "creates a connection based on config" do
|
53
|
+
expect(connection.url_prefix.to_s).to eq 'https://test-server/'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "sets the ssl options" do
|
57
|
+
expect(connection.ssl.ca_file).to eq ConnectionFactory::OSX_CERT_PATH
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when the machine is not OSX' do
|
62
|
+
|
63
|
+
let(:config_file) { 'spec/config/http_api_clients_with_ssl.yml' }
|
64
|
+
|
65
|
+
before { connection_factory.stub(osx?: false) }
|
66
|
+
|
67
|
+
it "creates a connection based on config" do
|
68
|
+
expect(connection.url_prefix.to_s).to eq 'https://test-server/'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "sets the ssl options" do
|
72
|
+
expect(connection.ssl.ca_path).to eq '/etc/ssl/certs'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'http basic auth' do
|
78
|
+
|
79
|
+
context 'when auth is not specified' do
|
80
|
+
|
81
|
+
let(:config_file) { 'spec/config/http_api_clients.yml' }
|
82
|
+
|
83
|
+
it 'does not set the auth on the connection' do
|
84
|
+
expect(connection.headers[:Authorization]).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when auth is specified' do
|
89
|
+
|
90
|
+
let(:config_file) { 'spec/config/http_api_clients_with_basic_auth.yml' }
|
91
|
+
|
92
|
+
it 'sets the auth on the connection' do
|
93
|
+
expect(connection.headers[:Authorization]).to_not be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require 'http_api_client/errors'
|
3
|
+
|
4
|
+
module HttpApiClient
|
5
|
+
module Errors
|
6
|
+
|
7
|
+
describe BaseError do
|
8
|
+
|
9
|
+
let(:nested_error) { double('nested', message: 'nested_message') }
|
10
|
+
|
11
|
+
let(:error) { BaseError.new("message", "response_body", nested_error) }
|
12
|
+
|
13
|
+
it "stores the response_body" do
|
14
|
+
expect(error.response_body).to eq "response_body"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "stores the nested_error" do
|
18
|
+
expect(error.nested_error).to eq nested_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns the message in message (duh)" do
|
22
|
+
expect(error.message).to include "message"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the response_body in message" do
|
26
|
+
expect(error.message).to include "response_body"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns the nested_error in message" do
|
30
|
+
expect(error.message).to include "nested_message"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'http_api_client/timed_result'
|
4
|
+
|
5
|
+
module HttpApiClient
|
6
|
+
|
7
|
+
describe TimedResult do
|
8
|
+
|
9
|
+
context 'without extra log data' do
|
10
|
+
|
11
|
+
let(:request_id) { 'abc-123' }
|
12
|
+
let(:logger) { HttpApiClient.logger }
|
13
|
+
|
14
|
+
before do
|
15
|
+
TimedResult.stub(:millis_since).and_return 1000
|
16
|
+
Thread.current[:request_id] = request_id
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'logs event with base data' do
|
20
|
+
logger.should_receive(:info).with("event=my_event, request_id=#{request_id}, timing=#{1000}")
|
21
|
+
TimedResult.time('my_event') { }
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'logs event with extra data' do
|
25
|
+
logger.should_receive(:info).with("event=my_event, request_id=#{request_id}, timing=#{1000}, foo=bar")
|
26
|
+
TimedResult.time('my_event', { foo: 'bar' }) { }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|