escher 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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