escher 0.2.1 → 0.3.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.
data/lib/escher.rb CHANGED
@@ -1,3 +1,7 @@
1
- require 'escher/base'
1
+ require 'addressable/uri'
2
+ require 'time'
3
+ require 'digest'
4
+
2
5
  require 'escher/version'
3
- require 'escher/request'
6
+ require 'escher/request/factory'
7
+ require 'escher/auth'
@@ -0,0 +1,408 @@
1
+ require 'spec_helper'
2
+
3
+ module Escher
4
+ describe Auth do
5
+
6
+ test_suites = {
7
+ # 'get-header-key-duplicate',
8
+ # 'get-header-value-order',
9
+ aws4: %w(
10
+ get-header-value-trim
11
+ get-relative
12
+ get-relative-relative
13
+ get-slash
14
+ get-slash-dot-slash
15
+ get-slash-pointless-dot
16
+ get-slashes
17
+ get-space
18
+ get-unreserved
19
+ get-utf8
20
+ get-vanilla
21
+ get-vanilla-empty-query-key
22
+ get-vanilla-query
23
+ get-vanilla-query-order-key
24
+ get-vanilla-query-order-key-case
25
+ get-vanilla-query-order-value
26
+ get-vanilla-query-unreserved
27
+ get-vanilla-ut8-query
28
+ post-header-key-case
29
+ post-header-key-sort
30
+ post-header-value-case
31
+ post-vanilla
32
+ post-vanilla-empty-query-value
33
+ post-vanilla-query
34
+ post-vanilla-query-nonunreserved
35
+ post-vanilla-query-space
36
+ post-x-www-form-urlencoded
37
+ post-x-www-form-urlencoded-parameters
38
+ ),
39
+ emarsys: %w(
40
+ get-header-key-duplicate
41
+ post-header-key-order
42
+ post-header-value-spaces
43
+ post-header-value-spaces-within-quotes
44
+ )
45
+ }
46
+
47
+ ESCHER_AWS4_OPTIONS = {
48
+ algo_prefix: 'AWS4', vendor_key: 'AWS4', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date'
49
+ }
50
+
51
+ ESCHER_MIXED_OPTIONS = {
52
+ algo_prefix: 'EMS', vendor_key: 'EMS', hash_algo: 'SHA256', auth_header_name: 'Authorization', date_header_name: 'Date', clock_skew: 10
53
+ }
54
+
55
+ ESCHER_EMARSYS_OPTIONS = ESCHER_MIXED_OPTIONS.merge(auth_header_name: 'X-Ems-Auth', date_header_name: 'X-Ems-Date')
56
+
57
+ GOOD_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'
58
+
59
+
60
+
61
+ def key_db
62
+ {
63
+ 'AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY',
64
+ 'th3K3y' => 'very_secure',
65
+ }
66
+ end
67
+
68
+
69
+
70
+ def credential_scope
71
+ %w(us-east-1 host aws4_request)
72
+ end
73
+
74
+
75
+
76
+ def client
77
+ {:api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'}
78
+ end
79
+
80
+
81
+
82
+ test_suites.each do |suite, tests|
83
+ tests.each do |test|
84
+ it "should calculate canonicalized request for #{test} in #{suite}" do
85
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS)
86
+ method, request_uri, body, headers = read_request(suite, test)
87
+ headers_to_sign = headers.map { |k| k[0].downcase }
88
+ path, query_parts = escher.parse_uri(request_uri)
89
+ canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
90
+ check_canonicalized_request(canonicalized_request, suite, test)
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ test_suites.each do |suite, tests|
97
+ tests.each do |test|
98
+ it "should calculate string to sign for #{test} in #{suite}" do
99
+ method, request_uri, body, headers, date = read_request(suite, test)
100
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
101
+ headers_to_sign = headers.map { |k| k[0].downcase }
102
+ path, query_parts = escher.parse_uri(request_uri)
103
+ canonicalized_request = escher.canonicalize(method, path, query_parts, body, headers, headers_to_sign)
104
+ string_to_sign = escher.get_string_to_sign(canonicalized_request)
105
+ expect(string_to_sign).to eq(fixture(suite, test, 'sts'))
106
+ end
107
+ end
108
+ end
109
+
110
+
111
+ test_suites.each do |suite, tests|
112
+ tests.each do |test|
113
+ it "should calculate auth header for #{test} in #{suite}" do
114
+ method, request_uri, body, headers, date = read_request(suite, test)
115
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse(date)))
116
+ headers_to_sign = headers.map { |k| k[0].downcase }
117
+ request = {
118
+ method: method,
119
+ uri: request_uri,
120
+ body: body,
121
+ headers: headers,
122
+ }
123
+ signed_headers = escher.sign!(request, client, headers_to_sign)[:headers].map { |k, v| {k.downcase => v} }.reduce({}, &:merge)
124
+ expect(signed_headers['authorization']).to eq(fixture(suite, test, 'authz'))
125
+ end
126
+ end
127
+ end
128
+
129
+
130
+ it 'should sign request' do
131
+ escher = described_class.new('us-east-1/iam/aws4_request', ESCHER_EMARSYS_OPTIONS.merge(current_time: Time.parse('20110909T233600Z')))
132
+ client = {:api_key_id => 'AKIDEXAMPLE', :api_secret => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'}
133
+
134
+ input_headers = [
135
+ ['host', 'iam.amazonaws.com'],
136
+ ['content-type', 'application/x-www-form-urlencoded; charset=utf-8'],
137
+ ]
138
+
139
+ expected_headers = {
140
+ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8',
141
+ 'host' => 'iam.amazonaws.com',
142
+ 'x-ems-date' => '20110909T233600Z',
143
+ 'x-ems-auth' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd',
144
+ }
145
+ headers_to_sign = %w(content-type)
146
+
147
+ request = {
148
+ method: 'POST',
149
+ uri: '/',
150
+ body: 'Action=ListUsers&Version=2010-05-08',
151
+ headers: input_headers,
152
+ }
153
+
154
+ downcase = escher.sign!(request, client, headers_to_sign)[:headers].map { |k, v| {k.downcase => v} }.reduce({}, &:merge)
155
+ expect(downcase).to eq expected_headers
156
+ end
157
+
158
+
159
+ it 'should generate presigned url' do
160
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/11 12:00:00 UTC')))
161
+ expected_url =
162
+ 'http://example.com/something?foo=bar&' + 'baz=barbaz&' +
163
+ 'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
164
+ 'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
165
+ 'X-EMS-Date=20110511T120000Z&' +
166
+ 'X-EMS-Expires=123456&' +
167
+ 'X-EMS-SignedHeaders=host&' +
168
+ 'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
169
+
170
+ client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
171
+ expect(escher.generate_signed_url('http://example.com/something?foo=bar&baz=barbaz', client, 123456)).to eq expected_url
172
+ end
173
+
174
+
175
+ it 'should validate presigned url' do
176
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 21:59:00 UTC')))
177
+ presigned_uri =
178
+ '/something?foo=bar&' + 'baz=barbaz&' +
179
+ 'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
180
+ 'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
181
+ 'X-EMS-Date=20110511T120000Z&' +
182
+ 'X-EMS-Expires=123456&' +
183
+ 'X-EMS-SignedHeaders=host&' +
184
+ 'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
185
+
186
+ client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
187
+ expect { escher.authenticate({
188
+ :method => 'GET',
189
+ :headers => [%w(host example.com)],
190
+ :uri => presigned_uri,
191
+ :body => 'IRRELEVANT'
192
+ }, key_db) }.not_to raise_error
193
+ end
194
+
195
+
196
+ it 'should validate expiration' do
197
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_MIXED_OPTIONS.merge(current_time: Time.parse('2011/05/12 22:20:00 UTC')))
198
+ presigned_uri =
199
+ '/something?foo=bar&' + 'baz=barbaz&' +
200
+ 'X-EMS-Algorithm=EMS-HMAC-SHA256&' +
201
+ 'X-EMS-Credentials=th3K3y%2F20110511%2Fus-east-1%2Fhost%2Faws4_request&' +
202
+ 'X-EMS-Date=20110511T120000Z&' +
203
+ 'X-EMS-Expires=123456&' +
204
+ 'X-EMS-SignedHeaders=host&' +
205
+ 'X-EMS-Signature=fbc9dbb91670e84d04ad2ae7505f4f52ab3ff9e192b8233feeae57e9022c2b67'
206
+
207
+ client = {:api_key_id => 'th3K3y', :api_secret => 'very_secure'}
208
+ expect { escher.authenticate({
209
+ :method => 'GET',
210
+ :headers => [%w(host example.com)],
211
+ :uri => presigned_uri,
212
+ :body => 'IRRELEVANT'
213
+ }, key_db) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
214
+ end
215
+
216
+
217
+ it 'should validate request' do
218
+ headers = [
219
+ %w(Host host.foo.com),
220
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
221
+ ['Authorization', GOOD_AUTH_HEADER],
222
+ ]
223
+ expect { call_validate(headers) }.not_to raise_error
224
+ end
225
+
226
+
227
+ it 'should authenticate' do
228
+ headers = [
229
+ %w(Host host.foo.com),
230
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
231
+ ['Authorization', GOOD_AUTH_HEADER],
232
+ ]
233
+ expect(call_validate(headers)).to eq 'AKIDEXAMPLE'
234
+ end
235
+
236
+
237
+ it 'should detect if signatures do not match' do
238
+ headers = [
239
+ %w(Host host.foo.com),
240
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
241
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'],
242
+ ]
243
+ expect { call_validate(headers) }.to raise_error(EscherError, 'The signatures do not match')
244
+ end
245
+
246
+
247
+ it 'should detect if dates are not on the same day' do
248
+ yesterday = '08'
249
+ headers = [
250
+ %w(Host host.foo.com),
251
+ ['Date', "Mon, #{yesterday} Sep 2011 23:36:00 GMT"],
252
+ ['Authorization', GOOD_AUTH_HEADER],
253
+ ]
254
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid request date')
255
+ end
256
+
257
+
258
+ it 'should detect if date is not within the 15 minutes range' do
259
+ long_ago = '00'
260
+ headers = [
261
+ %w(Host host.foo.com),
262
+ ['Date', "Mon, 09 Sep 2011 23:#{long_ago}:00 GMT"],
263
+ ['Authorization', GOOD_AUTH_HEADER],
264
+ ]
265
+ expect { call_validate(headers) }.to raise_error(EscherError, 'The request date is not within the accepted time range')
266
+ end
267
+
268
+
269
+ it 'should detect missing host header' do
270
+ headers = [
271
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
272
+ ['Authorization', GOOD_AUTH_HEADER],
273
+ ]
274
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Host')
275
+ end
276
+
277
+
278
+ it 'should detect missing date header' do
279
+ headers = [
280
+ %w(Host host.foo.com),
281
+ ['Authorization', GOOD_AUTH_HEADER],
282
+ ]
283
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Date')
284
+ end
285
+
286
+
287
+ it 'should detect missing auth header' do
288
+ headers = [
289
+ %w(Host host.foo.com),
290
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
291
+ ]
292
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Missing header: Authorization')
293
+ end
294
+
295
+
296
+ it 'should detect malformed auth header' do
297
+ headers = [
298
+ %w(Host host.foo.com),
299
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
300
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=UNPARSABLE'],
301
+ ]
302
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
303
+ end
304
+
305
+
306
+ it 'should detect malformed credential scope' do
307
+ headers = [
308
+ %w(Host host.foo.com),
309
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
310
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=BAD-CREDENTIAL-SCOPE, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
311
+ ]
312
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Malformed authorization header')
313
+ end
314
+
315
+
316
+ it 'should check mandatory signed headers: host' do
317
+ headers = [
318
+ %w(Host host.foo.com),
319
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
320
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
321
+ ]
322
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Host header is not signed')
323
+ end
324
+
325
+
326
+ it 'should check mandatory signed headers: date' do
327
+ headers = [
328
+ %w(Host host.foo.com),
329
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
330
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
331
+ ]
332
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Date header is not signed')
333
+ end
334
+
335
+
336
+ it 'should check algorithm' do
337
+ headers = [
338
+ %w(Host host.foo.com),
339
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
340
+ ['Authorization', 'AWS4-HMAC-INVALID Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
341
+ ]
342
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Only SHA256 and SHA512 hash algorithms are allowed')
343
+ end
344
+
345
+
346
+ it 'should check credential scope' do
347
+ headers = [
348
+ %w(Host host.foo.com),
349
+ ['Date', 'Mon, 09 Sep 2011 23:36:00 GMT'],
350
+ ['Authorization', 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/INVALID/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470'],
351
+ ]
352
+ expect { call_validate(headers) }.to raise_error(EscherError, 'Invalid credentials')
353
+ end
354
+
355
+
356
+ it 'should convert dates' do
357
+ date_str = 'Fri, 09 Sep 2011 23:36:00 GMT'
358
+ expect(described_class.new('irrelevant', date_header_name: 'date', current_time: Time.parse(date_str)).format_date_for_header).to eq date_str
359
+ end
360
+
361
+
362
+
363
+ def call_validate(headers)
364
+ escher = described_class.new('us-east-1/host/aws4_request', ESCHER_AWS4_OPTIONS.merge(current_time: Time.parse('Mon, 09 Sep 2011 23:40:00 GMT')))
365
+ escher.authenticate({
366
+ :method => 'GET',
367
+ :headers => headers,
368
+ :uri => '/',
369
+ :body => '',
370
+ }, key_db)
371
+ end
372
+
373
+
374
+
375
+ def fixture(suite, test, extension)
376
+ open("spec/#{suite}_testsuite/#{test}.#{extension}").read
377
+ end
378
+
379
+
380
+
381
+ def get_host(headers)
382
+ headers.detect { |header| header[0].downcase == 'host' }[1]
383
+ end
384
+
385
+
386
+
387
+ def get_date(headers)
388
+ headers.detect { |header| header[0].downcase == 'date' }[1]
389
+ end
390
+
391
+
392
+
393
+ def read_request(suite, test, extension = 'req')
394
+ lines = (fixture(suite, test, extension) + "\n").lines.map(&:chomp)
395
+ method, uri = lines[0].split ' '
396
+ headers = lines[1..-3].map { |header| k, v = header.split(':', 2); [k, v] }
397
+ body = lines[-1]
398
+ [method, uri, body, headers, get_date(headers), get_host(headers)]
399
+ end
400
+
401
+
402
+
403
+ def check_canonicalized_request(creq, suite, test)
404
+ expect(creq).to eq(fixture(suite, test, 'creq'))
405
+ end
406
+
407
+ end
408
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'rack/request'
3
+
4
+ describe Escher::Request::Factory do
5
+
6
+ describe ".from_request" do
7
+ {{uri: "request uri"} => Escher::Request::HashRequest,
8
+ Struct.new(:uri) => Escher::Request::LegacyRequest,
9
+ Rack::Request.new({}) => Escher::Request::RackRequest}.each do |request, expected_class|
10
+
11
+ it "should return a #{expected_class.name} when the request to be wrapped is a #{request.class.name}" do
12
+ expect(expected_class).to receive(:new).with(request).and_return "#{expected_class.name} wrapping request"
13
+
14
+ expect(described_class.from_request request).to eq "#{expected_class.name} wrapping request"
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe Escher::Request::HashRequest do
4
+
5
+ let(:request) { {headers: [], uri: '/'} }
6
+ subject { described_class.new request }
7
+
8
+
9
+ describe "#request" do
10
+ it "should return the underlying request object" do
11
+ expect(subject.request).to eq request
12
+ end
13
+ end
14
+
15
+
16
+ describe "#headers" do
17
+ it "should return the request headers" do
18
+ request[:headers] = [['HOST', 'some host'],
19
+ ['SOME_HEADER', 'some header']]
20
+
21
+ expect(subject.headers).to eq [['HOST', 'some host'],
22
+ ['SOME-HEADER', 'some header']]
23
+ end
24
+ end
25
+
26
+
27
+ describe "#has_header?" do
28
+ it "should return true if request has specified header, false otherwise" do
29
+ request[:headers] = [['HOST', 'some host']]
30
+
31
+ expect(subject.has_header? 'host').to be_truthy
32
+ expect(subject.has_header? 'no-such-header').to be_falsey
33
+ end
34
+ end
35
+
36
+
37
+ describe "#header" do
38
+ it "should return the value for the requested header" do
39
+ request[:headers] = [['HOST', 'some host']]
40
+
41
+ expect(subject.header 'host').to eq 'some host'
42
+ end
43
+
44
+ it "should return nil if no such header exists" do
45
+ expect(subject.header 'host').to be_nil
46
+ end
47
+ end
48
+
49
+
50
+ describe "#set_header" do
51
+ it "should add the specified header to the request" do
52
+ subject.set_header 'TEST_HEADER', 'test value'
53
+
54
+ expect(subject.has_header? 'test-header').to be_truthy
55
+ expect(subject.header 'test-header').to eq 'test value'
56
+ end
57
+
58
+ it "should return nil if no such header exists" do
59
+ expect(subject.header 'no-such-header').to be_nil
60
+ end
61
+ end
62
+
63
+
64
+ describe "#method" do
65
+ it "should return the request method" do
66
+ request[:method] = 'GET'
67
+
68
+ expect(subject.method).to eq 'GET'
69
+ end
70
+ end
71
+
72
+
73
+ describe "#body" do
74
+ it "should return the request body" do
75
+ request[:body] = 'request body'
76
+
77
+ expect(subject.body).to eq 'request body'
78
+ end
79
+
80
+ it "should return empty string for no body" do
81
+ expect(subject.body).to eq ''
82
+ end
83
+ end
84
+
85
+
86
+ describe "#path" do
87
+ it "should return the request path" do
88
+ request[:uri] = '/resources/id?search=query'
89
+
90
+ expect(subject.path).to eq '/resources/id'
91
+ end
92
+
93
+ it "should return the original path unnormalized" do
94
+ request[:uri] = '//'
95
+
96
+ expect(subject.path).to eq '//'
97
+ end
98
+ end
99
+
100
+
101
+ describe "#query_values" do
102
+ it "should return the request query parameters as an array of key-value pairs" do
103
+ request[:uri] = '/resources/id?search=query&param=value'
104
+
105
+ expect(subject.query_values).to eq [['search', 'query'], ['param', 'value']]
106
+ end
107
+
108
+ it "should return the query parameters regardless of fragments" do
109
+ request[:uri] = "/?@\#$%^&+=/,?><`\";:\\|][{}"
110
+
111
+ expect(subject.query_values).to eq [["@\#$%^"], ["+", "/,?><`\";:\\|][{}"]]
112
+ end
113
+
114
+ it "should return an empty array if the request has no query parameters" do
115
+ request[:uri] = '/resources/id'
116
+
117
+ expect(subject.query_values).to eq []
118
+ end
119
+ end
120
+
121
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'rack/request'
3
+
4
+ describe Escher::Request::RackRequest do
5
+
6
+ let(:request_params) { {"PATH_INFO" => "/", } }
7
+ let(:request) { Rack::Request.new request_params }
8
+
9
+ subject { described_class.new request }
10
+
11
+
12
+ describe "#request" do
13
+ it "should return the underlying request object" do
14
+ expect(subject.request).to eq request
15
+ end
16
+ end
17
+
18
+
19
+ describe "#headers" do
20
+ it "should return only the HTTP request headers" do
21
+ request_params.merge! 'HTTP_HOST' => 'some host',
22
+ 'SOME_HEADER' => 'some header'
23
+
24
+ expect(subject.headers).to eq [['HOST', 'some host']]
25
+ end
26
+
27
+ it "should replace underscores with dashes in the header name" do
28
+ request_params.merge! 'HTTP_HOST_NAME' => 'some host'
29
+
30
+ expect(subject.headers).to eq [['HOST-NAME', 'some host']]
31
+ end
32
+ end
33
+
34
+
35
+ describe "#has_header?" do
36
+ it "should return true if request has specified header, false otherwise" do
37
+ request_params.merge! 'HTTP_HOST_NAME' => 'some host'
38
+
39
+ expect(subject.has_header? 'host-name').to be_truthy
40
+ expect(subject.has_header? 'no-such-header').to be_falsey
41
+ end
42
+ end
43
+
44
+
45
+ describe "#header" do
46
+ it "should return the value for the requested header" do
47
+ request_params.merge! 'HTTP_HOST' => 'some host'
48
+
49
+ expect(subject.header 'host').to eq 'some host'
50
+ end
51
+
52
+ it "should return nil if no such header exists" do
53
+ expect(subject.header 'host').to be_nil
54
+ end
55
+ end
56
+
57
+
58
+ describe "#method" do
59
+ it "should return the request method" do
60
+ request_params.merge! 'REQUEST_METHOD' => 'GET'
61
+
62
+ expect(subject.method).to eq 'GET'
63
+ end
64
+ end
65
+
66
+
67
+ describe "#body" do
68
+ it "should return the request body" do
69
+ request_params.merge! 'rack.input' => 'request body'
70
+
71
+ expect(subject.body).to eq 'request body'
72
+ end
73
+
74
+ it "should return empty string for no body" do
75
+ expect(subject.body).to eq ''
76
+ end
77
+ end
78
+
79
+
80
+ describe "#path" do
81
+ it "should return the request path" do
82
+ request_params.merge! 'REQUEST_PATH' => '/resources/id///'
83
+
84
+ expect(subject.path).to eq '/resources/id///'
85
+ end
86
+ end
87
+
88
+
89
+ describe "#query_values" do
90
+ it "should return the request query parameters as an array of key-value pairs" do
91
+ request_params.merge! 'QUERY_STRING' => 'search=query&param=value'
92
+
93
+ expect(subject.query_values).to eq [['search', 'query'], ['param', 'value']]
94
+ end
95
+
96
+ it "should return the query parameters regardless of fragments" do
97
+ request_params.merge! 'QUERY_STRING' => "@\#$%^&+=/,?><`\";:\\|][{}"
98
+
99
+ expect(subject.query_values).to eq [["@\#$%^"], ["+", "/,?><`\";:\\|][{}"]]
100
+ end
101
+
102
+ it "should return an empty array if the request has no query parameters" do
103
+ expect(subject.query_values).to eq []
104
+ end
105
+ end
106
+
107
+
108
+ describe "#set_header" do
109
+ it "should ignore calls" do
110
+ expect { subject.set_header 'test-header', 'test value' }.not_to raise_error
111
+ end
112
+ end
113
+
114
+ end