webmachine 1.6.0 → 2.0.0.beta

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