webmachine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1030 @@
1
+ require 'spec_helper'
2
+
3
+ describe Webmachine::Decision::Flow do
4
+ subject { Webmachine::Decision::FSM.new(resource, request, response) }
5
+ let(:method) { 'GET' }
6
+ let(:uri) { URI.parse("http://localhost/") }
7
+ let(:headers) { Webmachine::Headers.new }
8
+ let(:body) { "" }
9
+ let(:request) { Webmachine::Request.new(method, uri, headers, body) }
10
+ let(:response) { Webmachine::Response.new }
11
+ let(:default_resource) { resource_with }
12
+ let(:missing_resource) { missing_resource_with }
13
+
14
+ def resource_with(&block)
15
+ klass = Class.new(Webmachine::Resource) do
16
+ def to_html; "test resource"; end
17
+ end
18
+ klass.module_eval(&block) if block_given?
19
+ klass.new(request, response)
20
+ end
21
+
22
+ def missing_resource_with(&block)
23
+ resource_with do
24
+ def resource_exists?; false; end
25
+ self.module_eval(&block) if block
26
+ end
27
+ end
28
+
29
+ describe "#b13 (Service Available?)" do
30
+ let(:resource) do
31
+ resource_with do
32
+ attr_accessor :available
33
+ def service_available?; @available; end
34
+ end
35
+ end
36
+
37
+ it "should respond with 503 when the service is unavailable" do
38
+ resource.available = false
39
+ subject.run
40
+ response.code.should == 503
41
+ end
42
+ end
43
+
44
+ describe "#b12 (Known method?)" do
45
+ let(:resource) do
46
+ resource_with do
47
+ def known_methods; ['HEAD']; end
48
+ end
49
+ end
50
+
51
+ it "should respond with 501 when the method is unknown" do
52
+ subject.run
53
+ response.code.should == 501
54
+ end
55
+ end
56
+
57
+ describe "#b11 (URI too long?)" do
58
+ let(:resource) do
59
+ resource_with do
60
+ def uri_too_long?(uri); true; end
61
+ end
62
+ end
63
+
64
+ it "should respond with 414 when the URI is too long" do
65
+ subject.run
66
+ response.code.should == 414
67
+ end
68
+ end
69
+
70
+ describe "#b10 (Method allowed?)" do
71
+ let(:resource) do
72
+ resource_with do
73
+ def allowed_methods; ['POST']; end
74
+ end
75
+ end
76
+
77
+ it "should respond with 405 when the method is not allowed" do
78
+ subject.run
79
+ response.code.should == 405
80
+ response.headers['Allow'].should == "POST"
81
+ end
82
+ end
83
+
84
+ describe "#b9 (Malformed request?)" do
85
+ let(:resource) { resource_with { def malformed_request?; true; end } }
86
+
87
+ it "should respond with 400 when the request is malformed" do
88
+ subject.run
89
+ response.code.should == 400
90
+ end
91
+
92
+ context "when the Content-MD5 header is present" do
93
+ let(:resource) do
94
+ resource_with do
95
+ def allowed_methods; ['POST']; end;
96
+ def process_post; true; end;
97
+ attr_accessor :validation
98
+ def validate_content_checksum; @validation; end
99
+ end
100
+ end
101
+
102
+ let(:method) { "POST" }
103
+ let(:body) { "This is the body." }
104
+ let(:headers) { Webmachine::Headers["Content-Type" => "text/plain"] }
105
+
106
+ it "should respond with 400 when the request body does not match the header" do
107
+ headers['Content-MD5'] = "thiswillnotmatchthehash"
108
+ subject.run
109
+ response.code.should == 400
110
+ end
111
+
112
+ it "should respond with 400 when the resource invalidates the checksum" do
113
+ resource.validation = false
114
+ headers['Content-MD5'] = "thiswillnotmatchthehash"
115
+ subject.run
116
+ response.code.should == 400
117
+ end
118
+
119
+ it "should not respond with 400 when the resource validates the checksum" do
120
+ resource.validation = true
121
+ headers['Content-MD5'] = "thiswillnotmatchthehash"
122
+ subject.run
123
+ response.code.should_not == 400
124
+ end
125
+
126
+ it "should respond with the given code when the resource returns a code while validating" do
127
+ resource.validation = 500
128
+ headers['Content-MD5'] = "thiswillnotmatchthehash"
129
+ subject.run
130
+ response.code.should == 500
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "#b8 (Authorized?)" do
136
+ let(:resource) { resource_with { attr_accessor :auth; def is_authorized?(header); @auth; end } }
137
+
138
+ it "should reply with 401 when the client is unauthorized" do
139
+ resource.auth = false
140
+ subject.run
141
+ response.code.should == 401
142
+ end
143
+
144
+ it "should reply with 401 when the resource gives a challenge" do
145
+ resource.auth = "Basic realm=Webmachine"
146
+ subject.run
147
+ response.code.should == 401
148
+ response.headers['WWW-Authenticate'].should == "Basic realm=Webmachine"
149
+ end
150
+
151
+ it "should halt with the given code when the resource returns a status code" do
152
+ resource.auth = 400
153
+ subject.run
154
+ response.code.should == 400
155
+ end
156
+
157
+ it "should not reply with 401 when the client is authorized" do
158
+ resource.auth = true
159
+ subject.run
160
+ response.code.should_not == 401
161
+ end
162
+ end
163
+
164
+ describe "#b7 (Forbidden?)" do
165
+ let(:resource) { resource_with { attr_accessor :forbid; def forbidden?; @forbid; end } }
166
+
167
+ it "should reply with 403 when the request is forbidden" do
168
+ resource.forbid = true
169
+ subject.run
170
+ response.code.should == 403
171
+ end
172
+
173
+ it "should not reply with 403 when the request is permitted" do
174
+ resource.forbid = false
175
+ subject.run
176
+ response.code.should_not == 403
177
+ end
178
+
179
+ it "should halt with the given code when the resource returns a status code" do
180
+ resource.forbid = 400
181
+ subject.run
182
+ response.code.should == 400
183
+ end
184
+ end
185
+
186
+ describe "#b6 (Unsupported Content-* header?)" do
187
+ let(:resource) do
188
+ resource_with do
189
+ def valid_content_headers?(contents)
190
+ contents['Content-Fail'].nil?
191
+ end
192
+ end
193
+ end
194
+
195
+ it "should reply with 501 when an invalid Content-* header is present" do
196
+ headers['Content-Fail'] = "yup"
197
+ subject.run
198
+ response.code.should == 501
199
+ end
200
+
201
+ it "should not reply with 501 when all Content-* headers are valid" do
202
+ subject.run
203
+ response.code.should_not == 501
204
+ end
205
+ end
206
+
207
+ describe "#b5 (Known Content-Type?)" do
208
+ let(:method) { "POST" }
209
+ let(:body) { "This is the body." }
210
+ let(:resource) do
211
+ resource_with do
212
+ def known_content_type?(type) type !~ /unknown/; end;
213
+ def process_post; true; end
214
+ def allowed_methods; %w{POST}; end
215
+ end
216
+ end
217
+
218
+ before { headers['Content-Length'] = body.length.to_s }
219
+
220
+ it "should reply with 415 when the Content-Type is unknown" do
221
+ headers['Content-Type'] = "application/x-unknown-type"
222
+ subject.run
223
+ response.code.should == 415
224
+ end
225
+
226
+ it "should not reply with 415 when the Content-Type is known" do
227
+ headers['Content-Type'] = "text/plain"
228
+ subject.run
229
+ response.code.should_not == 415
230
+ end
231
+ end
232
+
233
+ describe "#b4 (Request entity too large?)" do
234
+ let(:resource) do
235
+ resource_with do
236
+ def allowed_methods; %w{POST}; end
237
+ def process_post; true; end
238
+ def valid_entity_length?(length); length.to_i < 100; end
239
+ end
240
+ end
241
+ let(:method) { "POST" }
242
+ before { headers['Content-Type'] = "text/plain"; headers['Content-Length'] = body.size.to_s }
243
+
244
+ context "when the request body is too large" do
245
+ let(:body) { "Big" * 100 }
246
+ it "should reply with 413" do
247
+ subject.run
248
+ response.code.should == 413
249
+ end
250
+ end
251
+
252
+ context "when the request body is not too large" do
253
+ let(:body) { "small" }
254
+
255
+ it "should not reply with 413" do
256
+ subject.run
257
+ response.code.should_not == 413
258
+ end
259
+ end
260
+ end
261
+
262
+ describe "#b3 (OPTIONS?)" do
263
+ let(:method){ "OPTIONS" }
264
+ let(:resource){ resource_with { def allowed_methods; %w[GET HEAD OPTIONS]; end } }
265
+ it "should reply with 200 when the request method is OPTIONS" do
266
+ subject.run
267
+ response.code.should == 200
268
+ end
269
+ end
270
+
271
+ describe "#c3, #c4 (Acceptable media types)" do
272
+ let(:resource) { default_resource }
273
+ context "when the Accept header exists" do
274
+ it "should reply with 406 when the type is unacceptable" do
275
+ headers['Accept'] = "text/plain"
276
+ subject.run
277
+ response.code.should == 406
278
+ end
279
+
280
+ it "should not reply with 406 when the type is acceptable" do
281
+ headers['Accept'] = "text/*"
282
+ subject.run
283
+ response.code.should_not == 406
284
+ response.headers['Content-Type'].should == "text/html"
285
+ end
286
+ end
287
+
288
+ context "when the Accept header does not exist" do
289
+ it "should not negotiate a media type" do
290
+ headers['Accept'].should be_nil
291
+ subject.should_not_receive(:c4)
292
+ subject.run
293
+ response.headers['Content-Type'].should == 'text/html'
294
+ end
295
+ end
296
+ end
297
+
298
+ describe "#d4, #d5 (Acceptable languages)" do
299
+ let(:resource) { resource_with { def languages_provided; %w{en-US fr}; end } }
300
+ context "when the Accept-Language header exists" do
301
+ it "should reply with 406 when the language is unacceptable" do
302
+ headers['Accept-Language'] = "es, de"
303
+ subject.run
304
+ response.code.should == 406
305
+ end
306
+
307
+ it "should not reply with 406 when the language is acceptable" do
308
+ headers['Accept-Language'] = "en-GB, en;q=0.7"
309
+ subject.run
310
+ response.code.should_not == 406
311
+ response.headers['Content-Language'].should == "en-US"
312
+ end
313
+ end
314
+
315
+ context "when the Accept-Language header is absent" do
316
+ it "should not negotiate the language" do
317
+ headers['Accept-Language'].should be_nil
318
+ subject.should_not_receive(:d5)
319
+ subject.run
320
+ response.headers['Content-Language'].should == 'en-US'
321
+ end
322
+ end
323
+ end
324
+
325
+ describe "#e5, #e6 (Acceptable charsets)" do
326
+ let(:resource) do
327
+ resource_with do
328
+ def charsets_provided
329
+ [["iso8859-1", :to_iso],["utf-8", :to_utf]];
330
+ end
331
+ def to_iso(chunk); chunk; end
332
+ def to_utf(chunk); chunk; end
333
+ end
334
+ end
335
+
336
+ context "when the Accept-Charset header exists" do
337
+ it "should reply with 406 when the charset is unacceptable" do
338
+ headers['Accept-Charset'] = "utf-16"
339
+ subject.run
340
+ response.code.should == 406
341
+ end
342
+
343
+ it "should not reply with 406 when the charset is acceptable" do
344
+ headers['Accept-Charset'] = "iso8859-1"
345
+ subject.run
346
+ response.code.should_not == 406
347
+ response.headers['Content-Type'].should == "text/html;charset=iso8859-1"
348
+ end
349
+ end
350
+
351
+ context "when the Accept-Charset header is absent" do
352
+ it "should not negotiate the language" do
353
+ headers['Accept-Charset'].should be_nil
354
+ subject.should_not_receive(:e6)
355
+ subject.run
356
+ response.headers['Content-Type'].should == 'text/html;charset=iso8859-1'
357
+ end
358
+ end
359
+ end
360
+
361
+ describe "#f6, #f7 (Acceptable encodings)" do
362
+ let(:resource) do
363
+ resource_with do
364
+ def encodings_provided
365
+ super.merge("gzip" => :encode_gzip)
366
+ end
367
+ end
368
+ end
369
+
370
+ context "when the Accept-Encoding header is present" do
371
+ it "should reply with 406 if the encoding is unacceptable" do
372
+ headers['Accept-Encoding'] = 'deflate, identity;q=0.0'
373
+ subject.run
374
+ response.code.should == 406
375
+ end
376
+
377
+ it "should not reply with 406 if the encoding is acceptable" do
378
+ headers['Accept-Encoding'] = 'gzip, deflate'
379
+ subject.run
380
+ response.code.should_not == 406
381
+ response.headers['Content-Encoding'].should == 'gzip'
382
+ # It should be compressed
383
+ response.body.should_not == 'test resource'
384
+ end
385
+ end
386
+
387
+ context "when the Accept-Encoding header is not present" do
388
+ it "should not negotiate an encoding" do
389
+ headers['Accept-Encoding'].should be_nil
390
+ subject.should_not_receive(:f7)
391
+ subject.run
392
+ response.code.should_not == 406
393
+ # It should not be compressed
394
+ response.body.should == 'test resource'
395
+ end
396
+ end
397
+ end
398
+
399
+ describe "#g7 (Resource exists?)" do
400
+ let(:resource) { resource_with { attr_accessor :exist; def resource_exists?; @exist; end } }
401
+
402
+ it "should not enter conditional requests if missing (and eventually reply with 404)" do
403
+ resource.exist = false
404
+ subject.should_not_receive(:g8)
405
+ subject.run
406
+ response.code.should == 404
407
+ end
408
+
409
+ it "should not reply with 404 if it does exist" do
410
+ resource.exist = true
411
+ subject.should_not_receive(:h7)
412
+ subject.run
413
+ response.code.should_not == 404
414
+ end
415
+ end
416
+
417
+ # Conditional requests/preconditions
418
+ describe "#g8, #g9, #g10 (ETag match)" do
419
+ let(:resource) { resource_with { def generate_etag; "etag"; end } }
420
+ it "should skip ETag matching when If-Match is missing" do
421
+ headers['If-Match'].should be_nil
422
+ subject.should_not_receive(:g9)
423
+ subject.should_not_receive(:g11)
424
+ subject.run
425
+ response.code.should_not == 412
426
+ end
427
+ it "should not reply with 304 when If-Match is *" do
428
+ headers['If-Match'] = "*"
429
+ subject.run
430
+ response.code.should_not == 412
431
+ end
432
+ it "should reply with 412 if the ETag is not in If-Match" do
433
+ headers['If-Match'] = '"notetag"'
434
+ subject.run
435
+ response.code.should == 412
436
+ end
437
+ it "should not reply with 412 if the ETag is in If-Match" do
438
+ headers['If-Match'] = '"etag"'
439
+ subject.run
440
+ response.code.should_not == 412
441
+ end
442
+ end
443
+
444
+ describe "#h10, #h11, #h12 (If-Unmodified-Since match [IUMS])" do
445
+ let(:resource) { resource_with { attr_accessor :now; def last_modified; @now; end } }
446
+ before { @now = resource.now = Time.now }
447
+
448
+ it "should skip LM matching if IUMS is missing" do
449
+ headers['If-Unmodified-Since'].should be_nil
450
+ subject.should_not_receive(:h11)
451
+ subject.should_not_receive(:h12)
452
+ subject.run
453
+ response.code.should_not == 412
454
+ end
455
+
456
+ it "should skip LM matching if IUMS is an invalid date" do
457
+ headers['If-Unmodified-Since'] = "garbage"
458
+ subject.should_not_receive(:h12)
459
+ subject.run
460
+ response.code.should_not == 412
461
+ end
462
+
463
+ it "should not reply with 412 if LM is <= IUMS" do
464
+ headers['If-Unmodified-Since'] = (@now + 100).httpdate
465
+ subject.run
466
+ response.code.should_not == 412
467
+ end
468
+
469
+ it "should reply with 412 if LM is > IUMS" do
470
+ headers['If-Unmodified-Since'] = (@now - 100).httpdate
471
+ subject.run
472
+ response.code.should == 412
473
+ end
474
+ end
475
+
476
+ describe "#i12, #i13, #k13, #j18 (If-None-Match match)" do
477
+ let(:resource) do
478
+ resource_with do
479
+ def generate_etag; "etag"; end;
480
+ def process_post; true; end
481
+ def allowed_methods; %w{GET HEAD POST}; end
482
+ end
483
+ end
484
+
485
+ it "should skip ETag matching if If-None-Match is missing" do
486
+ headers['If-None-Match'].should be_nil
487
+ %w{i13 k13 j18}.each do |m|
488
+ subject.should_not_receive(m.to_sym)
489
+ end
490
+ subject.run
491
+ [304, 412].should_not include(response.code)
492
+ end
493
+
494
+ it "should not reply with 412 or 304 if the ETag is not in If-None-Match" do
495
+ headers['If-None-Match'] = '"notetag"'
496
+ subject.run
497
+ [304, 412].should_not include(response.code)
498
+ end
499
+
500
+ context "when the method is GET or HEAD" do
501
+ let(:method){ %w{GET HEAD}[rand(1)] }
502
+ it "should reply with 304 when If-None-Match is *" do
503
+ headers['If-None-Match'] = '*'
504
+ end
505
+ it "should reply with 304 when the ETag is in If-None-Match" do
506
+ headers['If-None-Match'] = '"etag", "foobar"'
507
+ end
508
+ after { subject.run; response.code.should == 304 }
509
+ end
510
+
511
+ context "when the method is not GET or HEAD" do
512
+ let(:method){ "POST" }
513
+ let(:body) { "This is the body." }
514
+ let(:headers){ Webmachine::Headers["Content-Type" => "text/plain"] }
515
+
516
+ it "should reply with 412 when If-None-Match is *" do
517
+ headers['If-None-Match'] = '*'
518
+ end
519
+
520
+ it "should reply with 412 when the ETag is in If-None-Match" do
521
+ headers['If-None-Match'] = '"etag"'
522
+ end
523
+ after { subject.run; response.code.should == 412 }
524
+ end
525
+ end
526
+
527
+ describe "#l13, #l14, #l15, #l17 (If-Modified-Since match)" do
528
+ let(:resource) { resource_with { attr_accessor :now; def last_modified; @now; end } }
529
+ before { @now = resource.now = Time.now }
530
+ it "should skip LM matching if IMS is missing" do
531
+ headers['If-Modified-Since'].should be_nil
532
+ %w{l14 l15 l17}.each do |m|
533
+ subject.should_not_receive(m.to_sym)
534
+ end
535
+ subject.run
536
+ response.code.should_not == 304
537
+ end
538
+
539
+ it "should skip LM matching if IMS is an invalid date" do
540
+ headers['If-Modified-Since'] = "garbage"
541
+ %w{l15 l17}.each do |m|
542
+ subject.should_not_receive(m.to_sym)
543
+ end
544
+ subject.run
545
+ response.code.should_not == 304
546
+ end
547
+
548
+ it "should skip LM matching if IMS is later than current time" do
549
+ headers['If-Modified-Since'] = (@now + 1000).httpdate
550
+ subject.should_not_receive(:l17)
551
+ subject.run
552
+ response.code.should_not == 304
553
+ end
554
+
555
+ it "should reply with 304 if LM is <= IMS" do
556
+ headers['If-Modified-Since'] = (@now - 1).httpdate
557
+ resource.now = @now - 1000
558
+ subject.run
559
+ response.code.should == 304
560
+ end
561
+
562
+ it "should not reply with 304 if LM is > IMS" do
563
+ headers['If-Modified-Since'] = (@now - 1000).httpdate
564
+ subject.run
565
+ response.code.should_not == 304
566
+ end
567
+ end
568
+
569
+ # Resource missing branch (upper right)
570
+ describe "#h7 (If-Match: * exists?)" do
571
+ let(:resource) { missing_resource }
572
+ it "should reply with 412 when the If-Match header is *" do
573
+ headers['If-Match'] = '"*"'
574
+ subject.run
575
+ response.code.should == 412
576
+ end
577
+
578
+ it "should not reply with 412 when the If-Match header is missing or not *" do
579
+ headers['If-Match'] = ['"etag"', nil][rand(1)]
580
+ subject.run
581
+ response.code.should_not == 412
582
+ end
583
+ end
584
+
585
+ describe "#i7 (PUT?)" do
586
+ let(:resource) do
587
+ missing_resource_with do
588
+ def allowed_methods; %w{GET HEAD PUT POST}; end
589
+ def process_post; true; end
590
+ end
591
+ end
592
+ let(:body) { %W{GET HEAD DELETE}.include?(method) ? nil : "This is the body." }
593
+ before { headers['Content-Type'] = 'text/plain' }
594
+
595
+ context "when the method is PUT" do
596
+ let(:method){ "PUT" }
597
+
598
+ it "should not reach state k7" do
599
+ subject.should_not_receive(:k7)
600
+ subject.run
601
+ end
602
+
603
+ after { [404, 410, 303].should_not include(response.code) }
604
+ end
605
+
606
+ context "when the method is not PUT" do
607
+ let(:method){ %W{GET HEAD POST DELETE}[rand(4)] }
608
+
609
+ it "should not reach state i4" do
610
+ subject.should_not_receive(:i4)
611
+ subject.run
612
+ end
613
+
614
+ after { response.code.should_not == 409 }
615
+ end
616
+ end
617
+
618
+ describe "#i4 (Apply to a different URI?)" do
619
+ let(:resource) do
620
+ missing_resource_with do
621
+ attr_accessor :location
622
+ def moved_permanently?; @location; end
623
+ def allowed_methods; %w[PUT]; end
624
+ end
625
+ end
626
+ let(:method){ "PUT" }
627
+ let(:body){ "This is the body." }
628
+ let(:headers) { Webmachine::Headers["Content-Type" => "text/plain", "Content-Length" => body.size.to_s] }
629
+
630
+ it "should reply with 301 when the resource has moved" do
631
+ resource.location = URI.parse("http://localhost:8098/newuri")
632
+ subject.run
633
+ response.code.should == 301
634
+ response.headers['Location'].should == resource.location.to_s
635
+ end
636
+
637
+ it "should not reply with 301 when resource has not moved" do
638
+ resource.location = false
639
+ subject.run
640
+ response.code.should_not == 301
641
+ end
642
+ end
643
+
644
+ describe "Redirection (Resource previously existed)" do
645
+ let(:resource) do
646
+ missing_resource_with do
647
+ attr_writer :moved_perm, :moved_temp, :allow_missing
648
+ def previously_existed?; true; end
649
+ def moved_permanently?; @moved_perm; end
650
+ def moved_temporarily?; @moved_temp; end
651
+ def allow_missing_post?; @allow_missing; end
652
+ def allowed_methods; %W{GET POST}; end
653
+ def process_post; true; end
654
+ end
655
+ end
656
+ let(:method){ @method || "GET" }
657
+
658
+ describe "#k5 (Moved permanently?)" do
659
+ it "should reply with 301 when the resource has moved permanently" do
660
+ uri = resource.moved_perm = URI.parse("http://www.google.com/")
661
+ subject.run
662
+ response.code.should == 301
663
+ response.headers['Location'].should == uri.to_s
664
+ end
665
+ it "should not reply with 301 when the resource has not moved permanently" do
666
+ resource.moved_perm = false
667
+ subject.run
668
+ response.code.should_not == 301
669
+ end
670
+ end
671
+
672
+ describe "#l5 (Moved temporarily?)" do
673
+ before { resource.moved_perm = false }
674
+ it "should reply with 307 when the resource has moved temporarily" do
675
+ uri = resource.moved_temp = URI.parse("http://www.basho.com/")
676
+ subject.run
677
+ response.code.should == 307
678
+ response.headers['Location'].should == uri.to_s
679
+ end
680
+ it "should not reply with 307 when the resource has not moved temporarily" do
681
+ resource.moved_temp = false
682
+ subject.run
683
+ response.code.should_not == 307
684
+ end
685
+ end
686
+
687
+ describe "#m5 (POST?), #n5 (POST to missing resource?)" do
688
+ before { resource.moved_perm = resource.moved_temp = false }
689
+ it "should reply with 410 when the method is not POST" do
690
+ method.should_not == "POST"
691
+ subject.run
692
+ response.code.should == 410
693
+ end
694
+ it "should reply with 410 when the resource disallows missing POSTs" do
695
+ @method = "POST"
696
+ resource.allow_missing = false
697
+ subject.run
698
+ response.code.should == 410
699
+ end
700
+ it "should not reply with 410 when the resource allows missing POSTs" do
701
+ @method = "POST"
702
+ resource.allow_missing = true
703
+ subject.run
704
+ response.code.should == 410
705
+ end
706
+ end
707
+ end
708
+
709
+ describe "#l7 (POST?), #m7 (POST to missing resource?)" do
710
+ let(:resource) do
711
+ missing_resource_with do
712
+ attr_accessor :allow_missing
713
+ def allowed_methods; %W{GET POST}; end
714
+ def previously_existed?; false; end
715
+ def allow_missing_post?; @allow_missing; end
716
+ def process_post; true; end
717
+ end
718
+ end
719
+ let(:method){ @method || "GET" }
720
+ it "should reply with 404 when the method is not POST" do
721
+ method.should_not == "POST"
722
+ subject.run
723
+ response.code.should == 404
724
+ end
725
+ it "should reply with 404 when the resource disallows missing POSTs" do
726
+ @method = "POST"
727
+ resource.allow_missing = false
728
+ subject.run
729
+ response.code.should == 404
730
+ end
731
+ it "should not reply with 404 when the resource allows missing POSTs" do
732
+ @method = "POST"
733
+ resource.allow_missing = true
734
+ subject.run
735
+ response.code.should_not == 404
736
+ end
737
+ end
738
+
739
+ describe "#p3 (Conflict?)" do
740
+ let(:resource) do
741
+ missing_resource_with do
742
+ attr_writer :conflict
743
+ def allowed_methods; %W{PUT}; end
744
+ def is_conflict?; @conflict; end
745
+ end
746
+ end
747
+ let(:method){ "PUT" }
748
+ it "should reply with 409 if the resource is in conflict" do
749
+ resource.conflict = true
750
+ subject.run
751
+ response.code.should == 409
752
+ end
753
+ it "should not reply with 409 if the resource is in conflict" do
754
+ resource.conflict = false
755
+ subject.run
756
+ response.code.should_not == 409
757
+ end
758
+ end
759
+
760
+ # Bottom right
761
+ describe "#n11 (Redirect?)" do
762
+ let(:method) { "POST" }
763
+ let(:resource) do
764
+ resource_with do
765
+ attr_writer :new_loc, :exist
766
+ def allowed_methods; %w{POST}; end
767
+ def allow_missing_post?; true; end
768
+ def process_post
769
+ response.redirect_to(@new_loc) if @new_loc
770
+ true
771
+ end
772
+ end
773
+ end
774
+ [true, false].each do |e|
775
+ context "and the resource #{ e ? "does not exist" : 'exists'}" do
776
+ before { resource.exist = e }
777
+
778
+ it "should reply with 303 if the resource redirected" do
779
+ resource.new_loc = URI.parse("/foo/bar")
780
+ subject.run
781
+ response.code.should == 303
782
+ response.headers['Location'].should == "/foo/bar"
783
+ end
784
+
785
+ it "should not reply with 303 if the resource did not redirect" do
786
+ resource.new_loc = nil
787
+ subject.run
788
+ response.code.should_not == 303
789
+ end
790
+ end
791
+ end
792
+ end
793
+
794
+ describe "#p11 (New resource?)" do
795
+ let(:resource) do
796
+ resource_with do
797
+ attr_writer :exist, :new_loc, :create
798
+
799
+ def allowed_methods; %W{PUT POST}; end
800
+ def resource_exists?; @exist; end
801
+ def process_post; true; end
802
+ def allow_missing_post?; true; end
803
+ def post_is_create?; @create; end
804
+ def create_path; @new_loc; end
805
+ def content_types_accepted; [["text/plain", :accept_text]]; end
806
+ def accept_text
807
+ response.headers['Location'] = @new_loc.to_s if @new_loc
808
+ true
809
+ end
810
+ end
811
+ end
812
+ let(:body) { "new content" }
813
+ let(:headers){ Webmachine::Headers['content-type' => 'text/plain'] }
814
+
815
+ context "when the method is PUT" do
816
+ let(:method){ "PUT" }
817
+ [true, false].each do |e|
818
+ context "and the resource #{ e ? "does not exist" : 'exists'}" do
819
+ before { resource.exist = e }
820
+
821
+ it "should reply with 201 when the Location header has been set" do
822
+ resource.exist = e
823
+ resource.new_loc = "http://ruby-doc.org/"
824
+ subject.run
825
+ response.code.should == 201
826
+ end
827
+ it "should not reply with 201 when the Location header has been set" do
828
+ resource.exist = e
829
+ subject.run
830
+ response.headers['Location'].should be_nil
831
+ response.code.should_not == 201
832
+ end
833
+ end
834
+ end
835
+ end
836
+
837
+ context "when the method is POST" do
838
+ let(:method){ "POST" }
839
+ [true, false].each do |e|
840
+ context "and the resource #{ e ? 'exists' : "does not exist"}" do
841
+ before { resource.exist = e }
842
+ it "should reply with 201 when post_is_create is true and create_path returns a URI" do
843
+ resource.new_loc = created = "/foo/bar/baz"
844
+ resource.create = true
845
+ subject.run
846
+ response.code.should == 201
847
+ response.headers['Location'].should == created
848
+ end
849
+ it "should reply with 500 when post_is_create is true and create_path returns nil" do
850
+ resource.create = true
851
+ subject.run
852
+ response.code.should == 500
853
+ response.error.should_not be_nil
854
+ end
855
+ it "should not reply with 201 when post_is_create is false" do
856
+ resource.create = false
857
+ subject.run
858
+ response.code.should_not == 201
859
+ end
860
+ end
861
+ end
862
+ end
863
+ end
864
+
865
+ describe "#o14 (Conflict?)" do
866
+ let(:resource) do
867
+ resource_with do
868
+ attr_writer :conflict
869
+ def allowed_methods; %W{PUT}; end
870
+ def is_conflict?; @conflict; end
871
+ end
872
+ end
873
+ let(:method){ "PUT" }
874
+ it "should reply with 409 if the resource is in conflict" do
875
+ resource.conflict = true
876
+ subject.run
877
+ response.code.should == 409
878
+ end
879
+ it "should not reply with 409 if the resource is in conflict" do
880
+ resource.conflict = false
881
+ subject.run
882
+ response.code.should_not == 409
883
+ end
884
+ end
885
+
886
+ describe "#m16 (DELETE?), #m20 (Delete enacted?)" do
887
+ let(:method){ @method || "DELETE" }
888
+ let(:resource) do
889
+ resource_with do
890
+ attr_writer :deleted, :completed
891
+ def allowed_methods; %w{GET DELETE}; end
892
+ def delete_resource; @deleted; end
893
+ def delete_completed?; @completed; end
894
+ end
895
+ end
896
+ it "should not reply with 202 if the method is not DELETE" do
897
+ @method = "GET"
898
+ subject.run
899
+ response.code.should_not == 202
900
+ end
901
+ it "should reply with 500 if the DELETE fails" do
902
+ resource.deleted = false
903
+ subject.run
904
+ response.code.should == 500
905
+ end
906
+ it "should reply with 202 if the DELETE succeeds but is not complete" do
907
+ resource.deleted = true
908
+ resource.completed = false
909
+ subject.run
910
+ response.code.should == 202
911
+ end
912
+ it "should not reply with 202 if the DELETE succeeds and completes" do
913
+ resource.completed = resource.deleted = true
914
+ subject.run
915
+ response.code.should_not == 202
916
+ end
917
+ end
918
+
919
+ # These decisions are covered by dozens of other examples. Leaving
920
+ # commented for now.
921
+ # describe "#n16 (POST?)" do it; end
922
+ # describe "#o16 (PUT?)" do it; end
923
+
924
+ describe "#o18 (Multiple representations?)" do
925
+ let(:resource) do
926
+ resource_with do
927
+ attr_writer :exist, :multiple
928
+ def delete_resource
929
+ response.body = "Response content."
930
+ true
931
+ end
932
+ def delete_completed?; true; end
933
+ def allowed_methods; %{GET HEAD PUT POST DELETE}; end
934
+ def resource_exists?; @exist; end
935
+ def allow_missing_post?; true; end
936
+ def content_types_accepted; [[request.content_type, :accept_all]]; end
937
+ def multiple_choices?; @multiple; end
938
+ def process_post
939
+ response.body = "Response content."
940
+ true
941
+ end
942
+ def accept_all
943
+ response.body = "Response content."
944
+ true
945
+ end
946
+ end
947
+ end
948
+
949
+ [["GET", true],["HEAD", true],["PUT", true],["PUT", false],["POST",true],["POST",false],
950
+ ["DELETE", true]].each do |m, e|
951
+ context "when the method is #{m} and the resource #{e ? 'exists' : 'does not exist' }" do
952
+ let(:method){ m }
953
+ let(:body) { %W{PUT POST}.include?(m) ? "request body" : "" }
954
+ let(:headers) { %W{PUT POST}.include?(m) ? Webmachine::Headers['content-type' => 'text/plain'] : Webmachine::Headers.new }
955
+ before { resource.exist = e }
956
+ it "should reply with 200 if there are not multiple representations" do
957
+ resource.multiple = false
958
+ subject.run
959
+ puts response.error if response.code == 500
960
+ response.code.should == 200
961
+ end
962
+ it "should reply with 300 if there are multiple representations" do
963
+ resource.multiple = true
964
+ subject.run
965
+ puts response.error if response.code == 500
966
+ response.code.should == 300
967
+ end
968
+ end
969
+ end
970
+ end
971
+
972
+ describe "#o20 (Response has entity?)" do
973
+ let(:resource) do
974
+ resource_with do
975
+ attr_writer :exist, :body
976
+ def delete_resource; true; end
977
+ def delete_completed?; true; end
978
+ def allowed_methods; %{GET PUT POST DELETE}; end
979
+ def resource_exists?; @exist; end
980
+ def allow_missing_post?; true; end
981
+ def content_types_accepted; [[request.content_type, :accept_all]]; end
982
+ def process_post
983
+ response.body = @body if @body
984
+ true
985
+ end
986
+ def accept_all
987
+ response.body = @body if @body
988
+ true
989
+ end
990
+ end
991
+ end
992
+ let(:method) { @method || "GET" }
993
+ let(:headers) { %{PUT POST}.include?(method) ? Webmachine::Headers["content-type" => "text/plain"] : Webmachine::Headers.new }
994
+ let(:body) { %{PUT POST}.include?(method) ? "This is the body." : nil }
995
+ context "when a response body is present" do
996
+ before { resource.body = "Hello, world!" }
997
+ [
998
+ ["PUT", false],
999
+ ["POST", false],
1000
+ ["DELETE", true],
1001
+ ["POST", true],
1002
+ ["PUT", true]
1003
+ ].each do |m, e|
1004
+ it "should not reply with 204 (via exists:#{e}, #{m})" do
1005
+ @method = m
1006
+ resource.exist = e
1007
+ subject.run
1008
+ response.code.should_not == 204
1009
+ end
1010
+ end
1011
+ end
1012
+ context "when a response body is not present" do
1013
+ [
1014
+ ["PUT", false],
1015
+ ["POST", false],
1016
+ ["DELETE", true],
1017
+ ["POST", true],
1018
+ ["PUT", true]
1019
+ ].each do |m, e|
1020
+ it "should reply with 204 (via exists:#{e}, #{m})" do
1021
+ @method = m
1022
+ resource.exist = e
1023
+ subject.run
1024
+ response.code.should == 204
1025
+ response.trace.last.should == :o20
1026
+ end
1027
+ end
1028
+ end
1029
+ end
1030
+ end