httpadapter 0.2.1 → 1.0.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,633 @@
1
+ # Copyright (C) 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+ require 'spec/httpadapter/adapter_type_checking_spec'
17
+
18
+ require 'httpadapter/adapters/net_http'
19
+
20
+ if ([:enable_post_connection_check=, 'enable_post_connection_check='] &
21
+ Net::HTTP.instance_methods).empty?
22
+ class Net::HTTP
23
+ def enable_post_connection_check=(value)
24
+ # No-op
25
+ end
26
+ end
27
+ end
28
+
29
+ describe HTTPAdapter::NetHTTPAdapter do
30
+ before do
31
+ @adapter = HTTPAdapter::NetHTTPAdapter.new
32
+ end
33
+
34
+ it_should_behave_like 'adapter type-checking example'
35
+
36
+ describe 'when adapting a request' do
37
+ describe 'from Net::HTTP::Get' do
38
+ before do
39
+ @request = Net::HTTP::Get.new('/path/to/resource')
40
+ @request['Accept'] = 'application/json'
41
+ @result = @adapter.adapt_request(@request)
42
+ @method, @uri, @headers, @body = @result
43
+ end
44
+
45
+ it 'should convert the HTTP method properly' do
46
+ @method.should == 'GET'
47
+ end
48
+
49
+ it 'should convert the URI properly' do
50
+ @uri.should == '/path/to/resource'
51
+ end
52
+
53
+ it 'should convert the headers properly' do
54
+ accept = nil
55
+ @headers.each do |header, value|
56
+ header.should be_kind_of(String)
57
+ value.should be_kind_of(String)
58
+ accept = value if header == 'Accept'
59
+ end
60
+ accept.should == 'application/json'
61
+ end
62
+
63
+ it 'should convert the body properly' do
64
+ @body.each do |chunk|
65
+ chunk.should be_kind_of(String)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'from Net::HTTP::Get with http' do
71
+ before do
72
+ @request = Net::HTTP::Get.new('/path/to/resource')
73
+ @request['Host'] = 'www.example.com'
74
+ @request['Accept'] = 'application/json'
75
+ @request['X-Forwarded-Proto'] = 'http'
76
+ @result = @adapter.adapt_request(@request)
77
+ @method, @uri, @headers, @body = @result
78
+ end
79
+
80
+ it 'should convert the HTTP method properly' do
81
+ @method.should == 'GET'
82
+ end
83
+
84
+ it 'should convert the URI properly' do
85
+ @uri.should == 'http://www.example.com/path/to/resource'
86
+ end
87
+
88
+ it 'should convert the headers properly' do
89
+ accept = nil
90
+ @headers.each do |header, value|
91
+ header.should be_kind_of(String)
92
+ value.should be_kind_of(String)
93
+ accept = value if header == 'Accept'
94
+ end
95
+ accept.should == 'application/json'
96
+ end
97
+
98
+ it 'should convert the body properly' do
99
+ @body.each do |chunk|
100
+ chunk.should be_kind_of(String)
101
+ end
102
+ end
103
+ end
104
+
105
+ describe 'from Net::HTTP::Get with https' do
106
+ before do
107
+ @request = Net::HTTP::Get.new('/path/to/resource')
108
+ @request['Host'] = 'www.example.com'
109
+ @request['Accept'] = 'application/json'
110
+ @request['X-Forwarded-Proto'] = 'https'
111
+ @result = @adapter.adapt_request(@request)
112
+ @method, @uri, @headers, @body = @result
113
+ end
114
+
115
+ it 'should convert the HTTP method properly' do
116
+ @method.should == 'GET'
117
+ end
118
+
119
+ it 'should convert the URI properly' do
120
+ @uri.should == 'https://www.example.com/path/to/resource'
121
+ end
122
+
123
+ it 'should convert the headers properly' do
124
+ accept = nil
125
+ @headers.each do |header, value|
126
+ header.should be_kind_of(String)
127
+ value.should be_kind_of(String)
128
+ accept = value if header == 'Accept'
129
+ end
130
+ accept.should == 'application/json'
131
+ end
132
+
133
+ it 'should convert the body properly' do
134
+ @body.each do |chunk|
135
+ chunk.should be_kind_of(String)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe 'from Net::HTTP::Post' do
141
+ before do
142
+ @request = Net::HTTP::Post.new('/path/to/resource')
143
+ @request['Accept'] = 'application/json'
144
+ @request['Content-Type'] = 'application/json; charset=utf-8'
145
+ @request.body = '{"three":3,"two":2,"one":1}'
146
+ @result = @adapter.adapt_request(@request)
147
+ @method, @uri, @headers, @body = @result
148
+ end
149
+
150
+ it 'should convert the HTTP method properly' do
151
+ @method.should == 'POST'
152
+ end
153
+
154
+ it 'should convert the URI properly' do
155
+ @uri.should == '/path/to/resource'
156
+ end
157
+
158
+ it 'should convert the headers properly' do
159
+ accept = nil
160
+ content_type = nil
161
+ @headers.each do |header, value|
162
+ header.should be_kind_of(String)
163
+ value.should be_kind_of(String)
164
+ accept = value if header == 'Accept'
165
+ content_type = value if header == 'Content-Type'
166
+ end
167
+ accept.should == 'application/json'
168
+ content_type.should == 'application/json; charset=utf-8'
169
+ end
170
+
171
+ it 'should convert the body properly' do
172
+ merged_body = ""
173
+ @body.each do |chunk|
174
+ chunk.should be_kind_of(String)
175
+ merged_body += chunk
176
+ end
177
+ merged_body.should == '{"three":3,"two":2,"one":1}'
178
+ end
179
+ end
180
+ end
181
+
182
+ describe 'when specializing a request' do
183
+ it 'should raise an error for converting from an invalid tuple' do
184
+ # Can't put this in the generic type-checking specs; this type-check
185
+ # is implementation-specific.
186
+ (lambda do
187
+ @adapter.specialize_request(['GET', '/', [], [42]])
188
+ end).should raise_error(TypeError)
189
+ end
190
+
191
+ it 'should raise an error for the bogus HTTP method' do
192
+ (lambda do
193
+ tuple = [
194
+ 'BOGUS',
195
+ 'http://www.example.com/path?query',
196
+ [
197
+ ['Accept', 'application/json'],
198
+ ['Content-Type', 'application/json; charset=utf-8'],
199
+ ['Content-Length', '27']
200
+ ],
201
+ ['{"three":3,', '"two":2,', '"one":1}']
202
+ ]
203
+ @adapter.specialize_request(tuple)
204
+ end).should raise_error(ArgumentError)
205
+ end
206
+
207
+ describe 'from a GET tuple' do
208
+ before do
209
+ @tuple = [
210
+ 'GET',
211
+ 'http://www.example.com/path?query',
212
+ [
213
+ ['Accept', 'application/json']
214
+ ],
215
+ []
216
+ ]
217
+ @request = @adapter.specialize_request(@tuple)
218
+ end
219
+
220
+ it 'should convert the HTTP method properly' do
221
+ @request.method.should == 'GET'
222
+ end
223
+
224
+ it 'should convert the URI properly' do
225
+ @request.path.should == '/path?query'
226
+ end
227
+
228
+ it 'should convert the headers properly' do
229
+ accept = nil
230
+ @request.canonical_each do |header, value|
231
+ header.should be_kind_of(String)
232
+ value.should be_kind_of(String)
233
+ accept = value if header == 'Accept'
234
+ end
235
+ accept.should == 'application/json'
236
+ end
237
+
238
+ it 'should convert the body properly' do
239
+ # Net::HTTP is weird in that it treats nils like empty strings.
240
+ [nil, ''].should include(@request.body)
241
+ [nil, 0].should include(@request.content_length)
242
+ end
243
+ end
244
+
245
+ describe 'from a GET tuple with a Host header' do
246
+ before do
247
+ @tuple = [
248
+ 'GET',
249
+ '/path?query',
250
+ [
251
+ ['Host', 'www.example.com'],
252
+ ['Accept', 'application/json']
253
+ ],
254
+ []
255
+ ]
256
+ @request = @adapter.specialize_request(@tuple)
257
+ end
258
+
259
+ it 'should convert the HTTP method properly' do
260
+ @request.method.should == 'GET'
261
+ end
262
+
263
+ it 'should convert the URI properly' do
264
+ @request.path.should == '/path?query'
265
+ end
266
+
267
+ it 'should convert the headers properly' do
268
+ accept = nil
269
+ host = nil
270
+ @request.canonical_each do |header, value|
271
+ header.should be_kind_of(String)
272
+ value.should be_kind_of(String)
273
+ host = value if header == 'Host'
274
+ accept = value if header == 'Accept'
275
+ end
276
+ host.should == 'www.example.com'
277
+ accept.should == 'application/json'
278
+ end
279
+
280
+ it 'should convert the body properly' do
281
+ # Net::HTTP is weird in that it treats nils like empty strings.
282
+ [nil, ''].should include(@request.body)
283
+ [nil, 0].should include(@request.content_length)
284
+ end
285
+ end
286
+
287
+ describe 'from a POST tuple' do
288
+ before do
289
+ @tuple = [
290
+ 'POST',
291
+ 'http://www.example.com/path?query',
292
+ [
293
+ ['Accept', 'application/json'],
294
+ ['Content-Type', 'application/json; charset=utf-8'],
295
+ ['Content-Length', '27']
296
+ ],
297
+ ['{"three":3,', '"two":2,', '"one":1}']
298
+ ]
299
+ @request = @adapter.specialize_request(@tuple)
300
+ end
301
+
302
+ it 'should convert the HTTP method properly' do
303
+ @request.method.should == 'POST'
304
+ end
305
+
306
+ it 'should convert the URI properly' do
307
+ @request.path.should == '/path?query'
308
+ end
309
+
310
+ it 'should convert the headers properly' do
311
+ accept = nil
312
+ content_type = nil
313
+ @request.canonical_each do |header, value|
314
+ header.should be_kind_of(String)
315
+ value.should be_kind_of(String)
316
+ accept = value if header == 'Accept'
317
+ content_type = value if header == 'Content-Type'
318
+ end
319
+ accept.should == 'application/json'
320
+ content_type.should == 'application/json; charset=utf-8'
321
+ end
322
+
323
+ it 'should convert the body properly' do
324
+ @request.body.should == '{"three":3,"two":2,"one":1}'
325
+ end
326
+ end
327
+
328
+ describe 'from a POST tuple with no body' do
329
+ before do
330
+ @tuple = [
331
+ 'POST',
332
+ 'http://www.example.com/path?query',
333
+ [
334
+ ['Accept', 'application/json']
335
+ ],
336
+ []
337
+ ]
338
+ @request = @adapter.specialize_request(@tuple)
339
+ end
340
+
341
+ it 'should convert the HTTP method properly' do
342
+ @request.method.should == 'POST'
343
+ end
344
+
345
+ it 'should convert the URI properly' do
346
+ @request.path.should == '/path?query'
347
+ end
348
+
349
+ it 'should convert the headers properly' do
350
+ accept = nil
351
+ @request.canonical_each do |header, value|
352
+ header.should be_kind_of(String)
353
+ value.should be_kind_of(String)
354
+ accept = value if header == 'Accept'
355
+ end
356
+ accept.should == 'application/json'
357
+ end
358
+
359
+ it 'should have the correct content length' do
360
+ @request.content_length.should == 0
361
+ end
362
+ end
363
+ end
364
+
365
+ describe 'when adapting a response' do
366
+ describe 'from Net::HTTPOK' do
367
+ before do
368
+ @response = Net::HTTPOK.new('1.1', '200', 'OK')
369
+ @response['Content-Type'] = 'application/json; charset=utf-8'
370
+ @response['Content-Length'] = '27'
371
+
372
+ # Ugh
373
+ @response.instance_variable_set('@read', true)
374
+ @response.instance_variable_set('@body', '{"three":3,"two":2,"one":1}')
375
+
376
+ @result = @adapter.adapt_response(@response)
377
+ @status, @headers, @body = @result
378
+ end
379
+
380
+ it 'should convert the HTTP status properly' do
381
+ @status.should == 200
382
+ end
383
+
384
+ it 'should convert the headers properly' do
385
+ content_type = nil
386
+ @headers.each do |header, value|
387
+ header.should be_kind_of(String)
388
+ value.should be_kind_of(String)
389
+ content_type = value if header == 'Content-Type'
390
+ end
391
+ content_type.should == 'application/json; charset=utf-8'
392
+ end
393
+
394
+ it 'should convert the body properly' do
395
+ merged_body = ""
396
+ @body.each do |chunk|
397
+ merged_body += chunk
398
+ chunk.should be_kind_of(String)
399
+ end
400
+ merged_body.should == '{"three":3,"two":2,"one":1}'
401
+ end
402
+ end
403
+ end
404
+
405
+ describe 'when specializing a response' do
406
+ it 'should raise an error for the bogus HTTP status code' do
407
+ (lambda do
408
+ tuple = [
409
+ 'BOGUS',
410
+ [
411
+ ['Content-Type', 'application/json; charset=utf-8'],
412
+ ['Content-Length', '27']
413
+ ],
414
+ ['{"three":3,', '"two":2,', '"one":1}']
415
+ ]
416
+ @adapter.specialize_response(tuple)
417
+ end).should raise_error(ArgumentError)
418
+ end
419
+
420
+ it 'should raise an error for converting from an invalid tuple' do
421
+ # Can't put this in the generic type-checking specs; this type-check
422
+ # is implementation-specific.
423
+ (lambda do
424
+ @adapter.specialize_response([200, [], [42]])
425
+ end).should raise_error(TypeError)
426
+ end
427
+
428
+ describe 'from a 200 tuple' do
429
+ before do
430
+ @tuple = [
431
+ 200,
432
+ [
433
+ ['Content-Type', 'application/json; charset=utf-8'],
434
+ ['Content-Length', '27']
435
+ ],
436
+ ['{"three":3,"two":2,"one":1}']
437
+ ]
438
+ @response = @adapter.specialize_response(@tuple)
439
+ end
440
+
441
+ it 'should be the correct type' do
442
+ @response.should be_kind_of(Net::HTTPOK)
443
+ end
444
+
445
+ it 'should convert the HTTP status properly' do
446
+ @response.code.to_i.should == 200
447
+ end
448
+
449
+ it 'should convert the headers properly' do
450
+ content_type = nil
451
+ @response.canonical_each do |header, value|
452
+ header.should be_kind_of(String)
453
+ value.should be_kind_of(String)
454
+ content_type = value if header == 'Content-Type'
455
+ end
456
+ content_type.should == 'application/json; charset=utf-8'
457
+ end
458
+
459
+ it 'should convert the body properly' do
460
+ @response.body.should == '{"three":3,"two":2,"one":1}'
461
+ end
462
+ end
463
+
464
+ describe 'from a 200 tuple' do
465
+ before do
466
+ @tuple = [
467
+ 500,
468
+ [
469
+ ['Content-Type', 'text/html'],
470
+ ['Content-Length', '28']
471
+ ],
472
+ ['<html><body>', '42', '</body></html>']
473
+ ]
474
+ @response = @adapter.specialize_response(@tuple)
475
+ end
476
+
477
+ it 'should be the correct type' do
478
+ @response.should be_kind_of(Net::HTTPInternalServerError)
479
+ end
480
+
481
+ it 'should convert the HTTP status properly' do
482
+ @response.code.to_i.should == 500
483
+ end
484
+
485
+ it 'should convert the headers properly' do
486
+ content_type = nil
487
+ @response.canonical_each do |header, value|
488
+ header.should be_kind_of(String)
489
+ value.should be_kind_of(String)
490
+ content_type = value if header == 'Content-Type'
491
+ end
492
+ content_type.should == 'text/html'
493
+ end
494
+
495
+ it 'should convert the body properly' do
496
+ @response.body.should == '<html><body>42</body></html>'
497
+ end
498
+ end
499
+ end
500
+
501
+ describe 'when transmitting a request' do
502
+ describe 'with a GET tuple' do
503
+ before do
504
+ @tuple = [
505
+ 'GET',
506
+ 'http://www.google.com/',
507
+ [],
508
+ []
509
+ ]
510
+ @response = @adapter.transmit(@tuple)
511
+ @status, @headers, @chunked_body = @response
512
+ @body = ''
513
+ @chunked_body.each do |chunk|
514
+ @body += chunk
515
+ end
516
+ end
517
+
518
+ it 'should have the correct status' do
519
+ @status.should == 200
520
+ end
521
+
522
+ it 'should have response headers' do
523
+ @headers.should_not be_empty
524
+ end
525
+
526
+ it 'should have a response body' do
527
+ @body.length.should > 0
528
+ end
529
+ end
530
+
531
+ describe 'with a GET tuple' do
532
+ before do
533
+ @tuple = [
534
+ 'GET',
535
+ 'https://encrypted.google.com/',
536
+ [],
537
+ []
538
+ ]
539
+ @response = @adapter.transmit(@tuple)
540
+ @status, @headers, @chunked_body = @response
541
+ @body = ''
542
+ @chunked_body.each do |chunk|
543
+ @body += chunk
544
+ end
545
+ end
546
+
547
+ it 'should have the correct status' do
548
+ @status.should == 200
549
+ end
550
+
551
+ it 'should have response headers' do
552
+ @headers.should_not be_empty
553
+ end
554
+
555
+ it 'should have a response body' do
556
+ @body.length.should > 0
557
+ end
558
+ end
559
+
560
+ describe 'with a connection' do
561
+ before do
562
+ @connection = HTTPAdapter::Connection.new(
563
+ 'www.google.com', 80,
564
+ Net::HTTP.new('www.google.com', 80),
565
+ :open => [:start],
566
+ :close => [:finish]
567
+ )
568
+ @connection.open
569
+ @tuple = [
570
+ 'GET',
571
+ 'http://www.google.com/',
572
+ [],
573
+ []
574
+ ]
575
+ @response = @adapter.transmit(@tuple, @connection)
576
+ @status, @headers, @chunked_body = @response
577
+ @body = ''
578
+ @chunked_body.each do |chunk|
579
+ @body += chunk
580
+ end
581
+ end
582
+
583
+ after do
584
+ @connection.close
585
+ end
586
+
587
+ it 'should have the correct status' do
588
+ @status.should == 200
589
+ end
590
+
591
+ it 'should have response headers' do
592
+ @headers.should_not be_empty
593
+ end
594
+
595
+ it 'should have a response body' do
596
+ @body.length.should > 0
597
+ end
598
+ end
599
+
600
+ describe 'with a custom configuration block' do
601
+ before do
602
+ @adapter = HTTPAdapter::NetHTTPAdapter.new do |http|
603
+ # You should never actually do this. But you could.
604
+ http.connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
605
+ end
606
+ @tuple = [
607
+ 'GET',
608
+ 'https://gmail.com/.well-known/host-meta',
609
+ [],
610
+ []
611
+ ]
612
+ @response = @adapter.transmit(@tuple, @connection)
613
+ @status, @headers, @chunked_body = @response
614
+ @body = ''
615
+ @chunked_body.each do |chunk|
616
+ @body += chunk
617
+ end
618
+ end
619
+
620
+ it 'should have the correct status' do
621
+ @status.should == 200
622
+ end
623
+
624
+ it 'should have response headers' do
625
+ @headers.should_not be_empty
626
+ end
627
+
628
+ it 'should have a response body' do
629
+ @body.length.should > 0
630
+ end
631
+ end
632
+ end
633
+ end