jeff 0.4.2 → 0.4.3
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.
- data/README.md +3 -20
- data/lib/jeff.rb +2 -162
- data/lib/jeff/secret.rb +1 -0
- data/lib/jeff/serviceable.rb +184 -0
- data/lib/jeff/version.rb +1 -1
- data/spec/jeff/serviceable_spec.rb +169 -0
- metadata +5 -4
- data/spec/jeff_spec.rb +0 -153
data/README.md
CHANGED
@@ -2,36 +2,19 @@
|
|
2
2
|
|
3
3
|
[![travis][stat]][trav]
|
4
4
|
|
5
|
-
**Jeff**
|
5
|
+
**Jeff** mixes in [authentication][sign] and other client behaviour for some
|
6
|
+
[Amazon Web Services (AWS)][aws].
|
6
7
|
|
7
8
|
![jeff][jeff]
|
8
9
|
|
9
10
|
## Usage
|
10
11
|
|
11
|
-
Mix in.
|
12
|
-
|
13
12
|
```ruby
|
14
|
-
class
|
13
|
+
class Service
|
15
14
|
include Jeff
|
16
15
|
end
|
17
16
|
```
|
18
17
|
|
19
|
-
Set endpoint and credentials.
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
client = Client.new
|
23
|
-
|
24
|
-
client.endpoint = 'http://example.com/path'
|
25
|
-
client.key = 'key'
|
26
|
-
client.secret = 'secret'
|
27
|
-
```
|
28
|
-
|
29
|
-
Request.
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
client.get query: { 'Foo' => 'Bar' }
|
33
|
-
```
|
34
|
-
|
35
18
|
[stat]: https://secure.travis-ci.org/papercavalier/jeff.png
|
36
19
|
[trav]: http://travis-ci.org/papercavalier/jeff
|
37
20
|
[aws]: http://aws.amazon.com/
|
data/lib/jeff.rb
CHANGED
@@ -1,167 +1,7 @@
|
|
1
|
-
require '
|
2
|
-
require 'time'
|
1
|
+
require 'jeff/serviceable'
|
3
2
|
|
4
|
-
require 'jeff/secret'
|
5
|
-
require 'jeff/version'
|
6
|
-
|
7
|
-
# Jeff is a light-weight module that mixes in client behaviour for Amazon Web
|
8
|
-
# Services (AWS).
|
9
3
|
module Jeff
|
10
|
-
USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
|
11
|
-
|
12
|
-
MissingEndpoint = Class.new ArgumentError
|
13
|
-
MissingKey = Class.new ArgumentError
|
14
|
-
MissingSecret = Class.new ArgumentError
|
15
|
-
|
16
|
-
UNRESERVED = /([^\w.~-]+)/
|
17
|
-
|
18
4
|
def self.included(base)
|
19
|
-
base.
|
20
|
-
|
21
|
-
base.headers 'User-Agent' => USER_AGENT
|
22
|
-
|
23
|
-
base.params 'AWSAccessKeyId' => -> { key },
|
24
|
-
'SignatureVersion' => '2',
|
25
|
-
'SignatureMethod' => 'HmacSHA256',
|
26
|
-
'Timestamp' => -> { Time.now.utc.iso8601 }
|
27
|
-
end
|
28
|
-
|
29
|
-
# Internal: Builds a sorted query.
|
30
|
-
#
|
31
|
-
# hsh - A hash of query parameters specific to the request.
|
32
|
-
#
|
33
|
-
# Returns a query String.
|
34
|
-
def build_query(hsh)
|
35
|
-
params
|
36
|
-
.merge(hsh)
|
37
|
-
.sort
|
38
|
-
.map { |k, v| "#{k}=#{ escape v }" }
|
39
|
-
.join '&'
|
40
|
-
end
|
41
|
-
|
42
|
-
# Internal: Returns an Excon::Connection.
|
43
|
-
def connection
|
44
|
-
@connection ||= Excon.new endpoint, headers: headers, expects: 200
|
45
|
-
end
|
46
|
-
|
47
|
-
# Internal: Gets the String AWS endpoint.
|
48
|
-
#
|
49
|
-
# Raises a MissingEndpoint error if endpoint is missing.
|
50
|
-
def endpoint
|
51
|
-
@endpoint or raise MissingEndpoint
|
52
|
-
end
|
53
|
-
|
54
|
-
# Sets the String AWS endpoint.
|
55
|
-
attr_writer :endpoint
|
56
|
-
|
57
|
-
# Internal: Returns the Hash default headers.
|
58
|
-
def headers
|
59
|
-
self.class.headers
|
60
|
-
end
|
61
|
-
|
62
|
-
# Internal: Gets the String AWS access key id.
|
63
|
-
#
|
64
|
-
# Raises a MissingKey error if key is missing.
|
65
|
-
def key
|
66
|
-
@key or raise MissingKey
|
67
|
-
end
|
68
|
-
|
69
|
-
# Sets the String AWS access key id.
|
70
|
-
attr_writer :key
|
71
|
-
|
72
|
-
# Internal: Returns the Hash default request parameters.
|
73
|
-
def params
|
74
|
-
self.class.params.reduce({}) do |a, (k, v)|
|
75
|
-
a.update k => (v.respond_to?(:call) ? instance_exec(&v) : v)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# Internal: Gets the Jeff::Secret.
|
80
|
-
#
|
81
|
-
# Raises a MissingSecret error if secret is missing.
|
82
|
-
def secret
|
83
|
-
@secret or raise MissingSecret
|
84
|
-
end
|
85
|
-
|
86
|
-
# Sets the AWS secret key.
|
87
|
-
#
|
88
|
-
# key - A String secret.
|
89
|
-
#
|
90
|
-
# Returns a Jeff::Secret.
|
91
|
-
def secret=(key)
|
92
|
-
@secret = Secret.new key
|
93
|
-
end
|
94
|
-
|
95
|
-
# Generate HTTP request verb methods that sign queries and return response
|
96
|
-
# bodies as IO objects.
|
97
|
-
Excon::HTTP_VERBS.each do |method|
|
98
|
-
eval <<-DEF
|
99
|
-
def #{method}(opts = {})
|
100
|
-
opts.update method: :#{method}
|
101
|
-
connection.request sign opts
|
102
|
-
end
|
103
|
-
DEF
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
def sign(opts)
|
109
|
-
query = build_query opts[:query] || {}
|
110
|
-
|
111
|
-
string_to_sign = [
|
112
|
-
opts[:method].upcase,
|
113
|
-
connection_host,
|
114
|
-
opts[:path] || connection_path,
|
115
|
-
query
|
116
|
-
].join "\n"
|
117
|
-
signature = secret.sign string_to_sign
|
118
|
-
|
119
|
-
opts.update query: [
|
120
|
-
query,
|
121
|
-
"Signature=#{escape signature}"
|
122
|
-
].join('&')
|
123
|
-
end
|
124
|
-
|
125
|
-
def connection_host
|
126
|
-
[
|
127
|
-
connection.connection[:host],
|
128
|
-
connection.connection[:port]
|
129
|
-
].join ':'
|
130
|
-
end
|
131
|
-
|
132
|
-
def connection_path
|
133
|
-
connection.connection[:path]
|
134
|
-
end
|
135
|
-
|
136
|
-
def escape(val)
|
137
|
-
val.to_s.gsub(UNRESERVED) do
|
138
|
-
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
module ClassMethods
|
143
|
-
# Gets/Updates the default headers.
|
144
|
-
#
|
145
|
-
# hsh - A Hash of headers.
|
146
|
-
#
|
147
|
-
# Returns the Hash headers.
|
148
|
-
def headers(hsh = nil)
|
149
|
-
@headers ||= {}
|
150
|
-
@headers.update hsh if hsh
|
151
|
-
|
152
|
-
@headers
|
153
|
-
end
|
154
|
-
|
155
|
-
# Gets/Updates the default request parameters.
|
156
|
-
#
|
157
|
-
# hsh - A Hash of parameters (default: nil).
|
158
|
-
#
|
159
|
-
# Returns the Hash parameters.
|
160
|
-
def params(hsh = nil)
|
161
|
-
@params ||= {}
|
162
|
-
@params.update hsh if hsh
|
163
|
-
|
164
|
-
@params
|
165
|
-
end
|
5
|
+
base.send :include, Serviceable
|
166
6
|
end
|
167
7
|
end
|
data/lib/jeff/secret.rb
CHANGED
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'excon'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
require 'jeff/secret'
|
7
|
+
require 'jeff/version'
|
8
|
+
|
9
|
+
module Jeff
|
10
|
+
# Mixes in Amazon Web Services (AWS) client behaviour.
|
11
|
+
module Serviceable
|
12
|
+
MissingEndpoint = Class.new ArgumentError
|
13
|
+
MissingKey = Class.new ArgumentError
|
14
|
+
MissingSecret = Class.new ArgumentError
|
15
|
+
|
16
|
+
UNRESERVED = /([^\w.~-]+)/
|
17
|
+
|
18
|
+
# A User-Agent header that identifies the application, its version number,
|
19
|
+
# and programming language.
|
20
|
+
#
|
21
|
+
# Amazon recommends to include one in requests to AWS endpoints.
|
22
|
+
USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
|
23
|
+
|
24
|
+
def self.included(base)
|
25
|
+
base.extend ClassMethods
|
26
|
+
|
27
|
+
base.headers 'User-Agent' => USER_AGENT
|
28
|
+
|
29
|
+
base.params 'AWSAccessKeyId' => -> { key },
|
30
|
+
'SignatureVersion' => '2',
|
31
|
+
'SignatureMethod' => 'HmacSHA256',
|
32
|
+
'Timestamp' => -> { Time.now.utc.iso8601 }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Internal: Builds a sorted query.
|
36
|
+
#
|
37
|
+
# hsh - A hash of query parameters specific to the request.
|
38
|
+
#
|
39
|
+
# Returns a query String.
|
40
|
+
def build_query(hsh)
|
41
|
+
params
|
42
|
+
.merge(hsh)
|
43
|
+
.sort
|
44
|
+
.map { |k, v| "#{k}=#{ escape v }" }
|
45
|
+
.join '&'
|
46
|
+
end
|
47
|
+
|
48
|
+
# Internal: Returns an Excon::Connection.
|
49
|
+
def connection
|
50
|
+
@connection ||= Excon.new endpoint,
|
51
|
+
headers: headers,
|
52
|
+
expects: 200
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Gets the String AWS endpoint.
|
56
|
+
#
|
57
|
+
# Raises a MissingEndpoint error if endpoint is missing.
|
58
|
+
def endpoint
|
59
|
+
@endpoint or raise MissingEndpoint
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the String AWS endpoint.
|
63
|
+
attr_writer :endpoint
|
64
|
+
|
65
|
+
# Internal: Returns the Hash default headers.
|
66
|
+
def headers
|
67
|
+
self.class.headers
|
68
|
+
end
|
69
|
+
|
70
|
+
# Internal: Gets the String AWS access key id.
|
71
|
+
#
|
72
|
+
# Raises a MissingKey error if key is missing.
|
73
|
+
def key
|
74
|
+
@key or raise MissingKey
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sets the String AWS access key id.
|
78
|
+
attr_writer :key
|
79
|
+
|
80
|
+
# Internal: Returns the Hash default request parameters.
|
81
|
+
def params
|
82
|
+
self.class.params.reduce({}) do |a, (k, v)|
|
83
|
+
a.update k => (v.respond_to?(:call) ? instance_exec(&v) : v)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Internal: Gets the Jeff::Secret.
|
88
|
+
#
|
89
|
+
# Raises a MissingSecret error if secret is missing.
|
90
|
+
def secret
|
91
|
+
@secret or raise MissingSecret
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets the AWS secret key.
|
95
|
+
#
|
96
|
+
# key - A String secret.
|
97
|
+
#
|
98
|
+
# Returns a Jeff::Secret.
|
99
|
+
def secret=(key)
|
100
|
+
@secret = Secret.new key
|
101
|
+
end
|
102
|
+
|
103
|
+
# Generate HTTP request verb methods.
|
104
|
+
Excon::HTTP_VERBS.each do |method|
|
105
|
+
eval <<-DEF
|
106
|
+
def #{method}(opts = {})
|
107
|
+
opts.update method: :#{method}
|
108
|
+
if opts[:body]
|
109
|
+
opts[:headers] ||= {}
|
110
|
+
opts[:headers].update 'Content-MD5' => calculate_md5(opts[:body])
|
111
|
+
end
|
112
|
+
|
113
|
+
connection.request sign opts
|
114
|
+
end
|
115
|
+
DEF
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def calculate_md5(body)
|
121
|
+
Base64.encode64(Digest::MD5.digest(body)).strip
|
122
|
+
end
|
123
|
+
|
124
|
+
def connection_host
|
125
|
+
[
|
126
|
+
connection.connection[:host],
|
127
|
+
connection.connection[:port]
|
128
|
+
].join ':'
|
129
|
+
end
|
130
|
+
|
131
|
+
def connection_path
|
132
|
+
connection.connection[:path]
|
133
|
+
end
|
134
|
+
|
135
|
+
def escape(val)
|
136
|
+
val.to_s.gsub(UNRESERVED) do
|
137
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def sign(opts)
|
142
|
+
query = build_query opts[:query] || {}
|
143
|
+
|
144
|
+
string_to_sign = [
|
145
|
+
opts[:method].upcase,
|
146
|
+
connection_host,
|
147
|
+
opts[:path] || connection_path,
|
148
|
+
query
|
149
|
+
].join "\n"
|
150
|
+
signature = secret.sign string_to_sign
|
151
|
+
|
152
|
+
opts.update query: [
|
153
|
+
query,
|
154
|
+
"Signature=#{escape signature}"
|
155
|
+
].join('&')
|
156
|
+
end
|
157
|
+
|
158
|
+
module ClassMethods
|
159
|
+
# Gets/Updates the default headers.
|
160
|
+
#
|
161
|
+
# hsh - A Hash of headers.
|
162
|
+
#
|
163
|
+
# Returns the Hash headers.
|
164
|
+
def headers(hsh = nil)
|
165
|
+
@headers ||= {}
|
166
|
+
@headers.update hsh if hsh
|
167
|
+
|
168
|
+
@headers
|
169
|
+
end
|
170
|
+
|
171
|
+
# Gets/Updates the default request parameters.
|
172
|
+
#
|
173
|
+
# hsh - A Hash of parameters (default: nil).
|
174
|
+
#
|
175
|
+
# Returns the Hash parameters.
|
176
|
+
def params(hsh = nil)
|
177
|
+
@params ||= {}
|
178
|
+
@params.update hsh if hsh
|
179
|
+
|
180
|
+
@params
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/jeff/version.rb
CHANGED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Jeff
|
4
|
+
describe Serviceable do
|
5
|
+
let(:klass) { Class.new { include Jeff } }
|
6
|
+
let(:client) { klass.new }
|
7
|
+
|
8
|
+
describe '.headers' do
|
9
|
+
subject { klass.headers }
|
10
|
+
|
11
|
+
it { should have_key 'User-Agent' }
|
12
|
+
|
13
|
+
it 'should be configurable' do
|
14
|
+
klass.instance_eval do
|
15
|
+
headers 'Foo' => 'bar'
|
16
|
+
end
|
17
|
+
|
18
|
+
should have_key 'Foo'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.params' do
|
23
|
+
subject { klass.params }
|
24
|
+
|
25
|
+
it { should have_key 'AWSAccessKeyId' }
|
26
|
+
|
27
|
+
it { should have_key 'SignatureMethod' }
|
28
|
+
|
29
|
+
it { should have_key 'SignatureVersion' }
|
30
|
+
|
31
|
+
it { should have_key 'Timestamp' }
|
32
|
+
|
33
|
+
it 'should be configurable' do
|
34
|
+
klass.instance_eval do
|
35
|
+
params 'Foo' => 'bar'
|
36
|
+
end
|
37
|
+
|
38
|
+
should have_key 'Foo'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#endpoint' do
|
43
|
+
it 'should require a value' do
|
44
|
+
expect { client.endpoint }.to raise_error Serviceable::MissingEndpoint
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#key' do
|
49
|
+
it 'should require a value' do
|
50
|
+
expect { client.key }.to raise_error Serviceable::MissingKey
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#secret' do
|
55
|
+
it 'should require a value' do
|
56
|
+
expect { client.secret }.to raise_error Serviceable::MissingSecret
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'given a key' do
|
61
|
+
before do
|
62
|
+
client.key = 'key'
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#params' do
|
66
|
+
subject { client.params }
|
67
|
+
|
68
|
+
it 'should include the key' do
|
69
|
+
subject['AWSAccessKeyId'].should eql client.key
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should generate a timestamp' do
|
73
|
+
subject['Timestamp'].should be_a String
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#build_query' do
|
78
|
+
subject { client.build_query 'A10' => 1, 'A1' => 1 }
|
79
|
+
|
80
|
+
it 'should include default parameters' do
|
81
|
+
should match(/Timestamp/)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should sort lexicographically' do
|
85
|
+
should match(/^A1=1&A10=/)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'given an endpoint' do
|
91
|
+
before do
|
92
|
+
client.endpoint = 'http://slowapi.com/delay/0'
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#connection" do
|
96
|
+
subject { client.connection }
|
97
|
+
let(:headers) { subject.connection[:headers] }
|
98
|
+
|
99
|
+
it { should be_an Excon::Connection }
|
100
|
+
|
101
|
+
it 'should set default headers' do
|
102
|
+
headers.should eq klass.headers
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should cache itself' do
|
106
|
+
subject.should be client.connection
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'given an endpoint, key, and secret' do
|
112
|
+
before do
|
113
|
+
client.endpoint = 'http://slowapi.com/delay/0'
|
114
|
+
client.key = 'key'
|
115
|
+
client.secret = 'secret'
|
116
|
+
end
|
117
|
+
|
118
|
+
Excon::HTTP_VERBS.each do |method|
|
119
|
+
describe "##{method}" do
|
120
|
+
subject { client.send(method, mock: true).body }
|
121
|
+
|
122
|
+
before do
|
123
|
+
Excon.stub({ method: method.to_sym }) do
|
124
|
+
{ body: method, status: 200 }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
after { Excon.stubs.clear }
|
129
|
+
|
130
|
+
it "should make a #{method.upcase} request" do
|
131
|
+
should eql method
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'given a request body' do
|
137
|
+
subject { client.get(mock: true, body: 'foo').body }
|
138
|
+
|
139
|
+
before do
|
140
|
+
Excon.stub({ method: :get }) do |params|
|
141
|
+
{ status: 200, body: params[:headers]['Content-MD5'] }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
after { Excon.stubs.clear }
|
146
|
+
|
147
|
+
it "should add an Content-MD5 header" do
|
148
|
+
should_not be_empty
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'given an HTTP status error' do
|
153
|
+
before do
|
154
|
+
Excon.stub({ method: :get }) do
|
155
|
+
{ status: 503 }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
after { Excon.stubs.clear }
|
160
|
+
|
161
|
+
it "should raise an error" do
|
162
|
+
expect {
|
163
|
+
client.get mock: true
|
164
|
+
}.to raise_error Excon::Errors::HTTPStatusError
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jeff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: excon
|
@@ -75,9 +75,10 @@ files:
|
|
75
75
|
- jeff.gemspec
|
76
76
|
- lib/jeff.rb
|
77
77
|
- lib/jeff/secret.rb
|
78
|
+
- lib/jeff/serviceable.rb
|
78
79
|
- lib/jeff/version.rb
|
79
80
|
- spec/jeff/secret_spec.rb
|
80
|
-
- spec/
|
81
|
+
- spec/jeff/serviceable_spec.rb
|
81
82
|
- spec/spec_helper.rb
|
82
83
|
homepage: https://github.com/papercavalier/jeff
|
83
84
|
licenses: []
|
@@ -105,6 +106,6 @@ specification_version: 3
|
|
105
106
|
summary: AWS client
|
106
107
|
test_files:
|
107
108
|
- spec/jeff/secret_spec.rb
|
108
|
-
- spec/
|
109
|
+
- spec/jeff/serviceable_spec.rb
|
109
110
|
- spec/spec_helper.rb
|
110
111
|
has_rdoc:
|
data/spec/jeff_spec.rb
DELETED
@@ -1,153 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Jeff do
|
4
|
-
let(:klass) { Class.new { include Jeff } }
|
5
|
-
let(:client) { klass.new }
|
6
|
-
|
7
|
-
describe '.headers' do
|
8
|
-
subject { klass.headers }
|
9
|
-
|
10
|
-
it { should have_key 'User-Agent' }
|
11
|
-
|
12
|
-
it 'should be configurable' do
|
13
|
-
klass.instance_eval do
|
14
|
-
headers 'Foo' => 'bar'
|
15
|
-
end
|
16
|
-
|
17
|
-
should have_key 'Foo'
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '.params' do
|
22
|
-
subject { klass.params }
|
23
|
-
|
24
|
-
it { should have_key 'AWSAccessKeyId' }
|
25
|
-
|
26
|
-
it { should have_key 'SignatureMethod' }
|
27
|
-
|
28
|
-
it { should have_key 'SignatureVersion' }
|
29
|
-
|
30
|
-
it { should have_key 'Timestamp' }
|
31
|
-
|
32
|
-
it 'should be configurable' do
|
33
|
-
klass.instance_eval do
|
34
|
-
params 'Foo' => 'bar'
|
35
|
-
end
|
36
|
-
|
37
|
-
should have_key 'Foo'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe '#endpoint' do
|
42
|
-
it 'should require a value' do
|
43
|
-
expect { client.endpoint }.to raise_error Jeff::MissingEndpoint
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
describe '#key' do
|
48
|
-
it 'should require a value' do
|
49
|
-
expect { client.key }.to raise_error Jeff::MissingKey
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe '#secret' do
|
54
|
-
it 'should require a value' do
|
55
|
-
expect { client.secret }.to raise_error Jeff::MissingSecret
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'given a key' do
|
60
|
-
before do
|
61
|
-
client.key = 'key'
|
62
|
-
end
|
63
|
-
|
64
|
-
describe '#params' do
|
65
|
-
subject { client.params }
|
66
|
-
|
67
|
-
it 'should include the key' do
|
68
|
-
subject['AWSAccessKeyId'].should eql client.key
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'should generate a timestamp' do
|
72
|
-
subject['Timestamp'].should be_a String
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe '#build_query' do
|
77
|
-
subject { client.build_query 'A10' => 1, 'A1' => 1 }
|
78
|
-
|
79
|
-
it 'should include default parameters' do
|
80
|
-
should match /Timestamp/
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'should sort lexicographically' do
|
84
|
-
should match /^A1=1&A10=/
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'given an endpoint' do
|
90
|
-
before do
|
91
|
-
client.endpoint = 'http://slowapi.com/delay/0'
|
92
|
-
end
|
93
|
-
|
94
|
-
describe "#connection" do
|
95
|
-
subject { client.connection }
|
96
|
-
let(:headers) { subject.connection[:headers] }
|
97
|
-
|
98
|
-
it { should be_an Excon::Connection }
|
99
|
-
|
100
|
-
it 'should set default headers' do
|
101
|
-
headers.should eq klass.headers
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'should cache itself' do
|
105
|
-
subject.should be client.connection
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'given an endpoint, key, and secret' do
|
111
|
-
before do
|
112
|
-
client.endpoint = 'http://slowapi.com/delay/0'
|
113
|
-
client.key = 'key'
|
114
|
-
client.secret = 'secret'
|
115
|
-
end
|
116
|
-
|
117
|
-
Excon::HTTP_VERBS.each do |method|
|
118
|
-
describe "##{method}" do
|
119
|
-
subject do
|
120
|
-
client.send(method, mock: true).body
|
121
|
-
end
|
122
|
-
|
123
|
-
before do
|
124
|
-
Excon.stub({ method: method.to_sym }) do |params|
|
125
|
-
{ body: method, status: 200 }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
after { Excon.stubs.clear }
|
130
|
-
|
131
|
-
it "should make a #{method.upcase} request" do
|
132
|
-
should eql method
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
context 'given an HTTP status error' do
|
138
|
-
before do
|
139
|
-
Excon.stub({ method: :get }) do
|
140
|
-
{ status: 503 }
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
after { Excon.stubs.clear }
|
145
|
-
|
146
|
-
it "should raise an error" do
|
147
|
-
expect {
|
148
|
-
client.get mock: true
|
149
|
-
}.to raise_error Excon::Errors::HTTPStatusError
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|