webmachine 0.1.0 → 0.2.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.
Files changed (63) hide show
  1. data/Gemfile +11 -3
  2. data/README.md +55 -27
  3. data/lib/webmachine/adapters/mongrel.rb +84 -0
  4. data/lib/webmachine/adapters/webrick.rb +12 -3
  5. data/lib/webmachine/adapters.rb +1 -7
  6. data/lib/webmachine/configuration.rb +30 -0
  7. data/lib/webmachine/decision/conneg.rb +7 -72
  8. data/lib/webmachine/decision/flow.rb +13 -11
  9. data/lib/webmachine/decision/fsm.rb +1 -9
  10. data/lib/webmachine/decision/helpers.rb +27 -7
  11. data/lib/webmachine/errors.rb +1 -0
  12. data/lib/webmachine/headers.rb +12 -3
  13. data/lib/webmachine/locale/en.yml +2 -2
  14. data/lib/webmachine/media_type.rb +117 -0
  15. data/lib/webmachine/resource/callbacks.rb +9 -0
  16. data/lib/webmachine/streaming.rb +3 -3
  17. data/lib/webmachine/version.rb +1 -1
  18. data/lib/webmachine.rb +3 -1
  19. data/pkg/webmachine-0.1.0/Gemfile +16 -0
  20. data/pkg/webmachine-0.1.0/Guardfile +11 -0
  21. data/pkg/webmachine-0.1.0/README.md +90 -0
  22. data/pkg/webmachine-0.1.0/Rakefile +31 -0
  23. data/pkg/webmachine-0.1.0/examples/webrick.rb +19 -0
  24. data/pkg/webmachine-0.1.0/lib/webmachine/adapters/webrick.rb +74 -0
  25. data/pkg/webmachine-0.1.0/lib/webmachine/adapters.rb +15 -0
  26. data/pkg/webmachine-0.1.0/lib/webmachine/decision/conneg.rb +304 -0
  27. data/pkg/webmachine-0.1.0/lib/webmachine/decision/flow.rb +502 -0
  28. data/pkg/webmachine-0.1.0/lib/webmachine/decision/fsm.rb +79 -0
  29. data/pkg/webmachine-0.1.0/lib/webmachine/decision/helpers.rb +80 -0
  30. data/pkg/webmachine-0.1.0/lib/webmachine/decision.rb +12 -0
  31. data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher/route.rb +85 -0
  32. data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher.rb +40 -0
  33. data/pkg/webmachine-0.1.0/lib/webmachine/errors.rb +37 -0
  34. data/pkg/webmachine-0.1.0/lib/webmachine/headers.rb +16 -0
  35. data/pkg/webmachine-0.1.0/lib/webmachine/locale/en.yml +28 -0
  36. data/pkg/webmachine-0.1.0/lib/webmachine/request.rb +56 -0
  37. data/pkg/webmachine-0.1.0/lib/webmachine/resource/callbacks.rb +362 -0
  38. data/pkg/webmachine-0.1.0/lib/webmachine/resource/encodings.rb +36 -0
  39. data/pkg/webmachine-0.1.0/lib/webmachine/resource.rb +48 -0
  40. data/pkg/webmachine-0.1.0/lib/webmachine/response.rb +49 -0
  41. data/pkg/webmachine-0.1.0/lib/webmachine/streaming.rb +27 -0
  42. data/pkg/webmachine-0.1.0/lib/webmachine/translation.rb +11 -0
  43. data/pkg/webmachine-0.1.0/lib/webmachine/version.rb +4 -0
  44. data/pkg/webmachine-0.1.0/lib/webmachine.rb +19 -0
  45. data/pkg/webmachine-0.1.0/spec/spec_helper.rb +13 -0
  46. data/pkg/webmachine-0.1.0/spec/tests.org +57 -0
  47. data/pkg/webmachine-0.1.0/spec/webmachine/decision/conneg_spec.rb +152 -0
  48. data/pkg/webmachine-0.1.0/spec/webmachine/decision/flow_spec.rb +1030 -0
  49. data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher/route_spec.rb +109 -0
  50. data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher_spec.rb +34 -0
  51. data/pkg/webmachine-0.1.0/spec/webmachine/headers_spec.rb +19 -0
  52. data/pkg/webmachine-0.1.0/spec/webmachine/request_spec.rb +24 -0
  53. data/pkg/webmachine-0.1.0/webmachine.gemspec +44 -0
  54. data/pkg/webmachine-0.1.0.gem +0 -0
  55. data/spec/webmachine/configuration_spec.rb +27 -0
  56. data/spec/webmachine/decision/conneg_spec.rb +18 -11
  57. data/spec/webmachine/decision/flow_spec.rb +2 -0
  58. data/spec/webmachine/decision/helpers_spec.rb +105 -0
  59. data/spec/webmachine/errors_spec.rb +13 -0
  60. data/spec/webmachine/headers_spec.rb +2 -1
  61. data/spec/webmachine/media_type_spec.rb +78 -0
  62. data/webmachine.gemspec +4 -1
  63. metadata +69 -11
@@ -0,0 +1,27 @@
1
+ module Webmachine
2
+ class StreamingEncoder
3
+ def initialize(resource, encoder, charsetter, body)
4
+ @resource, @encoder, @charsetter, @body = resource, encoder, charsetter, body
5
+ end
6
+ end
7
+
8
+ class EnumerableEncoder < StreamingEncoder
9
+ include Enumerable
10
+
11
+ def each
12
+ body.each do |block|
13
+ yield @resource.send(@encoder, resource.send(@charsetter, block))
14
+ end
15
+ end
16
+ end
17
+
18
+ class CallableEncoder < StreamingEncoder
19
+ def call
20
+ @resource.send(@encoder, @resource.send(@charsetter, body.call))
21
+ end
22
+
23
+ def to_proc
24
+ method(:call).to_proc
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'i18n'
2
+
3
+ I18n.config.load_path << File.expand_path("../locale/en.yml", __FILE__)
4
+
5
+ module Webmachine
6
+ module Translation
7
+ def t(key, options={})
8
+ ::I18n.t(key, options.merge(:scope => :webmachine))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Webmachine
2
+ VERSION = "0.1.0"
3
+ SERVER_STRING = "Webmachine-Ruby/#{VERSION}"
4
+ end
@@ -0,0 +1,19 @@
1
+ require 'webmachine/headers'
2
+ require 'webmachine/request'
3
+ require 'webmachine/response'
4
+ require 'webmachine/errors'
5
+ require 'webmachine/decision'
6
+ require 'webmachine/streaming'
7
+ require 'webmachine/adapters'
8
+ require 'webmachine/dispatcher'
9
+ require 'webmachine/resource'
10
+ require 'webmachine/version'
11
+
12
+ # Webmachine is a toolkit for making well-behaved HTTP applications.
13
+ # It is based on the Erlang library of the same name.
14
+ module Webmachine
15
+ # Starts Webmachine serving requests
16
+ def self.run
17
+ Adapters.const_get(adapter).run
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH << File.expand_path("..", __FILE__)
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'webmachine'
6
+ require 'rspec'
7
+
8
+ RSpec.configure do |config|
9
+ config.mock_with :rspec
10
+ config.filter_run :focus => true
11
+ config.run_all_when_everything_filtered = true
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ end
@@ -0,0 +1,57 @@
1
+ * 2,2 Basic Rules
2
+ - HTTP/1.1 header field values can be folded onto multiple lines if
3
+ the continuation line begins with a space or horizontal tab. All
4
+ linear white space, including folding, has the same semantics as
5
+ SP. A recipient MAY replace any linear white space with a single
6
+ SP before interpreting the field value or forwarding the message
7
+ downstream.
8
+ - Many HTTP/1.1 header field values consist of words separated by
9
+ LWS or special characters. These special characters MUST be in a
10
+ quoted string to be used within a parameter value (as defined in
11
+ section 3.6).
12
+ * 3.1 HTTP Version
13
+ - Note that the major and minor numbers MUST be treated as separate
14
+ integers and that each MAY be incremented higher than a single
15
+ digit.
16
+ - Leading zeros MUST be ignored by recipients and MUST NOT be sent.
17
+ - An application that sends a request or response message that
18
+ includes HTTP-Version of "HTTP/1.1" MUST be at least conditionally
19
+ compliant with this specification.
20
+ - Applications that are at least conditionally compliant with this
21
+ specification SHOULD use an HTTP-Version of "HTTP/1.1" in their
22
+ messages, and MUST do so for any message that is not compatible
23
+ with HTTP/1.0.
24
+ - Since the protocol version indicates the protocol capability of
25
+ the sender, a proxy/gateway MUST NOT send a message with a version
26
+ indicator which is greater than its actual version. If a higher
27
+ version request is received, the proxy/gateway MUST either
28
+ downgrade the request version, or respond with an error, or switch
29
+ to tunnel behavior.
30
+ - Due to interoperability problems with HTTP/1.0 proxies discovered
31
+ since the publication of RFC 2068 [33], caching proxies MUST,
32
+ gateways MAY, and tunnels MUST NOT upgrade the request to the
33
+ highest version they support. The proxy/gateway's response to that
34
+ request MUST be in the same major version as the request.
35
+ * 3.2 URIs
36
+ ** General Syntax
37
+ - Servers MUST be able to handle the URI of any resource they serve.
38
+ - Servers SHOULD be able to handle URIs of unbounded length if they
39
+ provide GET-based forms that could generate such URIs.
40
+ - A server SHOULD return 414 (Request-URI Too Long) status if a URI
41
+ is longer than the server can handle
42
+ ** http URL
43
+ - If the abs_path is not present in the URL, it MUST be given as
44
+ "/" when used as a Request-URI for a resource.
45
+ - If a proxy receives a fully qualified domain name, the proxy MUST
46
+ NOT change the host name.
47
+ ** URI Comparison
48
+ - When comparing two URIs to decide if they match or not, a client
49
+ SHOULD use a case-sensitive octet-by-octet comparison of the
50
+ entire URIs, with these exceptions:
51
+ - A port that is empty or not given is equivalent to the default
52
+ port for that URI-reference
53
+ - Comparisons of host names MUST be case-insensitive
54
+ - Comparisons of scheme names MUST be case-insensitive
55
+ - An empty abs_path is equivalent to an abs_path of "/"
56
+ * 3.3 Date/Time Formats
57
+
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe Webmachine::Decision::Conneg do
4
+ let(:request) { Webmachine::Request.new("GET", URI.parse("http://localhost:8080/"), Webmachine::Headers["accept" => "*/*"], "") }
5
+ let(:response) { Webmachine::Response.new }
6
+ let(:resource) do
7
+ Class.new(Webmachine::Resource) do
8
+ def to_html; "hello world!"; end
9
+ end
10
+ end
11
+ subject do
12
+ Webmachine::Decision::FSM.new(resource, request, response)
13
+ end
14
+
15
+ context "choosing a media type" do
16
+ it "should not choose a type when none are provided" do
17
+ subject.choose_media_type([], "*/*").should be_nil
18
+ end
19
+
20
+ it "should not choose a type when none are acceptable" do
21
+ subject.choose_media_type(["text/html"], "application/json").should be_nil
22
+ end
23
+
24
+ it "should choose the first acceptable type" do
25
+ subject.choose_media_type(["text/html", "application/xml"],
26
+ "application/xml, text/html, */*").should == "application/xml"
27
+ end
28
+
29
+ it "should choose the type that matches closest when matching subparams" do
30
+ subject.choose_media_type(["text/html",
31
+ ["text/html", {"charset" => "iso8859-1"}]],
32
+ "text/html;charset=iso8859-1, application/xml").
33
+ should == "text/html;charset=iso8859-1"
34
+
35
+ end
36
+
37
+ it "should choose the preferred type over less-preferred types" do
38
+ subject.choose_media_type(["text/html", "application/xml"],
39
+ "application/xml;q=0.7, text/html, */*").should == "text/html"
40
+
41
+ end
42
+
43
+ it "should raise an exception when a media-type is improperly formatted" do
44
+ expect {
45
+ subject.choose_media_type(["text/html", "application/xml"],
46
+ "bah;")
47
+ }.to raise_error(Webmachine::MalformedRequest)
48
+ end
49
+ end
50
+
51
+ context "choosing an encoding" do
52
+ it "should not set the encoding when none are provided" do
53
+ subject.choose_encoding({}, "identity, gzip")
54
+ subject.metadata['Content-Encoding'].should be_nil
55
+ subject.response.headers['Content-Encoding'].should be_nil
56
+ end
57
+
58
+ it "should not set the Content-Encoding header when it is identity" do
59
+ subject.choose_encoding({"gzip"=> :encode_gzip, "identity" => :encode_identity}, "identity")
60
+ subject.metadata['Content-Encoding'].should == 'identity'
61
+ response.headers['Content-Encoding'].should be_nil
62
+ end
63
+
64
+ it "should choose the first acceptable encoding" do
65
+ subject.choose_encoding({"gzip" => :encode_gzip}, "identity, gzip")
66
+ subject.metadata['Content-Encoding'].should == 'gzip'
67
+ response.headers['Content-Encoding'].should == 'gzip'
68
+ end
69
+
70
+ it "should choose the preferred encoding over less-preferred encodings" do
71
+ subject.choose_encoding({"gzip" => :encode_gzip, "identity" => :encode_identity}, "gzip, identity;q=0.7")
72
+ subject.metadata['Content-Encoding'].should == 'gzip'
73
+ response.headers['Content-Encoding'].should == 'gzip'
74
+ end
75
+
76
+ it "should not set the encoding if none are acceptable" do
77
+ subject.choose_encoding({"gzip" => :encode_gzip}, "identity")
78
+ subject.metadata['Content-Encoding'].should be_nil
79
+ response.headers['Content-Encoding'].should be_nil
80
+ end
81
+ end
82
+
83
+ context "choosing a charset" do
84
+ it "should not set the charset when none are provided" do
85
+ subject.choose_charset([], "ISO-8859-1")
86
+ subject.metadata['Charset'].should be_nil
87
+ end
88
+
89
+ it "should choose the first acceptable charset" do
90
+ subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "US-ASCII, UTF-8")
91
+ subject.metadata['Charset'].should == "US-ASCII"
92
+ end
93
+
94
+ it "should choose the preferred charset over less-preferred charsets" do
95
+ subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "US-ASCII;q=0.7, UTF-8")
96
+ subject.metadata['Charset'].should == "UTF-8"
97
+ end
98
+
99
+ it "should not set the charset if none are acceptable" do
100
+ subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "ISO-8859-1")
101
+ subject.metadata['Charset'].should be_nil
102
+ end
103
+
104
+ it "should choose a charset case-insensitively" do
105
+ subject.choose_charset([["UtF-8", :to_utf8],["US-ASCII", :to_ascii]], "iso-8859-1, utf-8")
106
+ subject.metadata['Charset'].should == "utf-8"
107
+ end
108
+ end
109
+
110
+ context "choosing a language" do
111
+ it "should not set the language when none are provided" do
112
+ subject.choose_language([], "en")
113
+ subject.metadata['Language'].should be_nil
114
+ end
115
+
116
+ it "should choose the first acceptable language" do
117
+ subject.choose_language(['en', 'en-US', 'es'], "en-US, es")
118
+ subject.metadata['Language'].should == "en-US"
119
+ response.headers['Content-Language'].should == "en-US"
120
+ end
121
+
122
+ it "should choose the preferred language over less-preferred languages" do
123
+ subject.choose_language(['en', 'en-US', 'es'], "en-US;q=0.6, es")
124
+ subject.metadata['Language'].should == "es"
125
+ response.headers['Content-Language'].should == "es"
126
+ end
127
+
128
+ it "should select the first language if all are acceptable" do
129
+ subject.choose_language(['en', 'fr', 'es'], "*")
130
+ subject.metadata['Language'].should == "en"
131
+ response.headers['Content-Language'].should == "en"
132
+ end
133
+
134
+ it "should select the closest acceptable language when an exact match is not available" do
135
+ subject.choose_language(['en-US', 'es'], "en, fr")
136
+ subject.metadata['Language'].should == 'en-US'
137
+ response.headers['Content-Language'].should == 'en-US'
138
+ end
139
+
140
+ it "should not set the language if none are acceptable" do
141
+ subject.choose_language(['en'], 'es')
142
+ subject.metadata['Language'].should be_nil
143
+ response.headers.should_not include('Content-Language')
144
+ end
145
+
146
+ it "should choose a language case-insensitively" do
147
+ subject.choose_language(['en-US', 'ZH'], 'zh-ch, EN')
148
+ subject.metadata['Language'].should == 'en-US'
149
+ response.headers['Content-Language'].should == 'en-US'
150
+ end
151
+ end
152
+ end
@@ -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