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.
@@ -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,6 @@
1
+ test:
2
+ my_client:
3
+ protocol: 'http'
4
+ server: test-server
5
+ port: 80
6
+ base_uri: test-base-uri
@@ -0,0 +1,8 @@
1
+ test:
2
+ my_client:
3
+ protocol: 'https'
4
+ server: test-server
5
+ port: 443
6
+ base_uri: test-base-uri
7
+ http_basic_username: admin
8
+ http_basic_password: password
@@ -0,0 +1,7 @@
1
+ test:
2
+ my_client:
3
+ protocol: 'http'
4
+ server: test-server
5
+ port: 80
6
+ base_uri: test-base-uri
7
+ include_request_id_header: true
@@ -0,0 +1,7 @@
1
+ test:
2
+ my_client:
3
+ protocol: 'https'
4
+ server: test-server
5
+ port: 443
6
+ base_uri: test-base-uri
7
+ ca_file: /usr/local/etc/nginx/test-server.crt
@@ -0,0 +1,6 @@
1
+ test:
2
+ my_client:
3
+ protocol: 'https'
4
+ server: test-server
5
+ port: 443
6
+ base_uri: test-base-uri
@@ -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