doze 0.0.11

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 (50) hide show
  1. data/README +6 -0
  2. data/lib/doze/application.rb +92 -0
  3. data/lib/doze/collection/object.rb +14 -0
  4. data/lib/doze/entity.rb +62 -0
  5. data/lib/doze/error.rb +75 -0
  6. data/lib/doze/media_type.rb +135 -0
  7. data/lib/doze/negotiator.rb +107 -0
  8. data/lib/doze/request.rb +119 -0
  9. data/lib/doze/resource/error.rb +21 -0
  10. data/lib/doze/resource/proxy.rb +81 -0
  11. data/lib/doze/resource.rb +193 -0
  12. data/lib/doze/responder/error.rb +34 -0
  13. data/lib/doze/responder/main.rb +41 -0
  14. data/lib/doze/responder/resource.rb +262 -0
  15. data/lib/doze/responder.rb +58 -0
  16. data/lib/doze/response.rb +78 -0
  17. data/lib/doze/router/anchored_route_set.rb +68 -0
  18. data/lib/doze/router/route.rb +88 -0
  19. data/lib/doze/router/route_set.rb +34 -0
  20. data/lib/doze/router.rb +100 -0
  21. data/lib/doze/serialization/entity.rb +34 -0
  22. data/lib/doze/serialization/form_data_helpers.rb +40 -0
  23. data/lib/doze/serialization/html.rb +116 -0
  24. data/lib/doze/serialization/json.rb +29 -0
  25. data/lib/doze/serialization/multipart_form_data.rb +162 -0
  26. data/lib/doze/serialization/resource.rb +30 -0
  27. data/lib/doze/serialization/resource_proxy.rb +14 -0
  28. data/lib/doze/serialization/www_form_encoded.rb +42 -0
  29. data/lib/doze/serialization/yaml.rb +25 -0
  30. data/lib/doze/uri_template.rb +220 -0
  31. data/lib/doze/utils.rb +53 -0
  32. data/lib/doze/version.rb +3 -0
  33. data/lib/doze.rb +5 -0
  34. data/test/functional/auth_test.rb +69 -0
  35. data/test/functional/base.rb +159 -0
  36. data/test/functional/cache_header_test.rb +76 -0
  37. data/test/functional/direct_response_test.rb +16 -0
  38. data/test/functional/error_handling_test.rb +131 -0
  39. data/test/functional/get_and_conneg_test.rb +182 -0
  40. data/test/functional/media_type_extensions_test.rb +102 -0
  41. data/test/functional/media_type_test.rb +40 -0
  42. data/test/functional/method_support_test.rb +49 -0
  43. data/test/functional/non_get_method_test.rb +173 -0
  44. data/test/functional/precondition_test.rb +84 -0
  45. data/test/functional/raw_path_info_test.rb +69 -0
  46. data/test/functional/resource_representation_test.rb +14 -0
  47. data/test/functional/router_test.rb +196 -0
  48. data/test/functional/serialization_test.rb +142 -0
  49. data/test/functional/uri_template_test.rb +51 -0
  50. metadata +221 -0
@@ -0,0 +1,162 @@
1
+ require 'doze/media_type'
2
+ require 'doze/serialization/entity'
3
+ require 'doze/serialization/form_data_helpers'
4
+ require 'doze/error'
5
+ require 'doze/utils'
6
+ require 'tempfile'
7
+
8
+ module Doze::Serialization
9
+ # Also ripped off largely from Merb::Parse.
10
+ #
11
+ # Small differences in the hash it returns for an uploaded file - it will have string keys,
12
+ # use media_type rather than content_type (for consistency with rest of doze) and adds a temp_path
13
+ # key.
14
+ #
15
+ # These enable it to be used interchangably with nginx upload module if you use config like eg:
16
+ #
17
+ # upload_set_form_field $upload_field_name[filename] "$upload_file_name";
18
+ # upload_set_form_field $upload_field_name[media_type] "$upload_content_type";
19
+ # upload_set_form_field $upload_field_name[temp_path] "$upload_tmp_path";
20
+ # upload_aggregate_form_field $upload_field_name[size] "$upload_file_size";
21
+ #
22
+ class Entity::MultipartFormData < Entity
23
+ include FormDataHelpers
24
+
25
+ NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
26
+ CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
27
+ FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
28
+ CRLF = "\r\n".freeze
29
+ EOL = CRLF
30
+
31
+ def object_data(try_deserialize=true)
32
+ @object_data ||= if @lazy_object_data
33
+ @lazy_object_data.call
34
+ elsif try_deserialize
35
+ @binary_data_stream && deserialize_stream
36
+ end
37
+ end
38
+
39
+ def deserialize_stream
40
+ boundary = @media_type_params && @media_type_params['boundary'] or raise "missing boundary parameter for multipart/form-data"
41
+ boundary = "--#{boundary}"
42
+ paramhsh = {}
43
+ buf = ""
44
+ input = @binary_data_stream
45
+ input.binmode if defined? input.binmode
46
+ boundary_size = boundary.size + EOL.size
47
+ bufsize = 16384
48
+ length = @binary_data_length or raise "expected Content-Length for multipart/form-data"
49
+ length -= boundary_size
50
+ # status is boundary delimiter line
51
+ status = input.read(boundary_size)
52
+ return {} if status == nil || status.empty?
53
+ raise "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
54
+ # second argument to Regexp.quote is for KCODE
55
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
56
+ loop {
57
+ head = nil
58
+ body = ''
59
+ filename = content_type = name = nil
60
+ read_size = 0
61
+ until head && buf =~ rx
62
+ i = buf.index("\r\n\r\n")
63
+ if( i == nil && read_size == 0 && length == 0 )
64
+ length = -1
65
+ break
66
+ end
67
+ if !head && i
68
+ head = buf.slice!(0, i+2) # First \r\n
69
+ buf.slice!(0, 2) # Second \r\n
70
+
71
+ # String#[] with 2nd arg here is returning
72
+ # a group from match data
73
+ filename = head[FILENAME_REGEX, 1]
74
+ content_type = head[CONTENT_TYPE_REGEX, 1]
75
+ name = head[NAME_REGEX, 1]
76
+
77
+ if filename && !filename.empty?
78
+ body = Tempfile.new(:Doze)
79
+ body.binmode if defined? body.binmode
80
+ end
81
+ next
82
+ end
83
+
84
+ # Save the read body part.
85
+ if head && (boundary_size+4 < buf.size)
86
+ body << buf.slice!(0, buf.size - (boundary_size+4))
87
+ end
88
+
89
+ read_size = bufsize < length ? bufsize : length
90
+ if( read_size > 0 )
91
+ c = input.read(read_size)
92
+ raise "bad content body" if c.nil? || c.empty?
93
+ buf << c
94
+ length -= c.size
95
+ end
96
+ end
97
+
98
+ # Save the rest.
99
+ if i = buf.index(rx)
100
+ # correct value of i for some edge cases
101
+ if (i > 2) && (j = buf.index(rx, i-2)) && (j < i)
102
+ i = j
103
+ end
104
+ body << buf.slice!(0, i)
105
+ buf.slice!(0, boundary_size+2)
106
+
107
+ length = -1 if $1 == "--"
108
+ end
109
+
110
+ if filename && !filename.empty?
111
+ body.rewind
112
+ data = {
113
+ "filename" => File.basename(filename),
114
+ "media_type" => content_type,
115
+ "tempfile" => body,
116
+ "temp_path" => body.path,
117
+ "size" => File.size(body.path)
118
+ }
119
+ else
120
+ data = body
121
+ end
122
+ paramhsh = normalize_params(paramhsh,name,data)
123
+ break if buf.empty? || length == -1
124
+ }
125
+ paramhsh
126
+ end
127
+
128
+ # This is designed to work with either actual file upload fields, or the corresponding
129
+ # fields generated by nginx upload module as described above.
130
+ #
131
+ # yields or returns a Doze::Entity for the uploaded file, with the correct media_type and binary_data_length.
132
+ # ensures to close and unlinks the underlying tempfile afterwards where used with a block.
133
+ def param_entity(name)
134
+ meta = object_data[name]; return unless meta.is_a?(Hash)
135
+ media_type = meta["media_type"] and media_type = Doze::MediaType[media_type] or return
136
+ size = meta["size"] && meta["size"].to_i
137
+ if (tempfile = meta["tempfile"])
138
+ temp_path = tempfile.path
139
+ elsif (temp_path = meta["temp_path"])
140
+ tempfile = File.open(meta["temp_path"], "rb")
141
+ end
142
+ return unless tempfile
143
+ entity = media_type.new_entity(:binary_data_stream => tempfile, :binary_data_length => size)
144
+
145
+ return entity unless block_given?
146
+ begin
147
+ yield entity
148
+ ensure
149
+ tempfile.close
150
+ begin
151
+ File.unlink(temp_path)
152
+ rescue StandardError
153
+ # we made an effort to clean up - but it is a tempfile, so no biggie
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ # A browser-friendly media type for use with Doze::Serialization::Resource.
160
+ MULTIPART_FORM_DATA = Doze::MediaType.register('multipart/form-data', :entity_class => Entity::MultipartFormData)
161
+ end
162
+
@@ -0,0 +1,30 @@
1
+ require 'doze/serialization/json'
2
+ require 'doze/serialization/www_form_encoded'
3
+ require 'doze/serialization/multipart_form_data'
4
+ require 'doze/serialization/html'
5
+
6
+ # A resource whose representations are all serializations of some ruby data.
7
+ # A good example of how to do media type negotiation
8
+ module Doze::Serialization
9
+ module Resource
10
+ # You probably want to override these
11
+ def serialization_media_types
12
+ [JSON, HTML]
13
+ end
14
+
15
+ # Analogous to get, but returns data which may be serialized into entities of any one of serialization_media_types
16
+ def get_data
17
+ end
18
+
19
+ def get
20
+ serialization_media_types.map do |media_type|
21
+ media_type.entity_class.new(media_type, :lazy_object_data => proc {get_data})
22
+ end
23
+ end
24
+
25
+ # You may want to be more particular than this if you can only deal with certain serialization types
26
+ def accepts_method_with_media_type?(method, entity)
27
+ entity.is_a?(Doze::Serialization::Entity)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ require 'doze/serialization/resource'
2
+ require 'doze/resource/proxy'
3
+
4
+ class Doze::Serialization::ResourceProxy < Doze::Resource::Proxy
5
+ include Doze::Serialization::Resource
6
+
7
+ def serialization_media_types
8
+ target && target.serialization_media_types
9
+ end
10
+
11
+ def get_data
12
+ target && target.get_data
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ require 'doze/media_type'
2
+ require 'doze/serialization/entity'
3
+ require 'doze/serialization/form_data_helpers'
4
+ require 'doze/error'
5
+ require 'doze/utils'
6
+
7
+ module Doze::Serialization
8
+ # ripped off largely from Merb::Parse
9
+ # Supports PHP-style nested hashes via foo[bar][baz]=boz
10
+ class Entity::WWWFormEncoded < Entity
11
+ include FormDataHelpers
12
+
13
+ def serialize(value, prefix=nil)
14
+ case value
15
+ when Array
16
+ value.map {|v| serialize(v, "#{prefix}[]")}.join("&")
17
+ when Hash
18
+ value.map {|k,v| serialize(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))}.join("&")
19
+ else
20
+ "#{prefix}=#{escape(value)}"
21
+ end
22
+ end
23
+
24
+ def deserialize(data)
25
+ query = {}
26
+ for pair in data.split(/[&;] */n)
27
+ key, value = unescape(pair).split('=',2)
28
+ next if key.nil?
29
+ if key.include?('[')
30
+ normalize_params(query, key, value)
31
+ else
32
+ query[key] = value
33
+ end
34
+ end
35
+ query
36
+ end
37
+ end
38
+
39
+ # A browser-friendly media type for use with Doze::Serialization::Resource.
40
+ WWW_FORM_ENCODED = Doze::MediaType.register('application/x-www-form-urlencoded', :entity_class => Entity::WWWFormEncoded)
41
+ end
42
+
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+ require 'doze/media_type'
3
+ require 'doze/serialization/entity'
4
+ require 'doze/error'
5
+
6
+ # Note that it isn't safe to accept YAML input, unless you trust the sender, as
7
+ # it is possible to craft a YAML message to allow remote code execution (see
8
+ # cve-2013-0156)
9
+ module Doze::Serialization
10
+ class Entity::YAML < Entity
11
+ def serialize(ruby_data)
12
+ ruby_data.to_yaml
13
+ end
14
+
15
+ def deserialize(binary_data)
16
+ begin
17
+ ::YAML.load(binary_data)
18
+ rescue ::YAML::ParseError, ArgumentError
19
+ raise Doze::ClientEntityError, "Could not parse YAML"
20
+ end
21
+ end
22
+ end
23
+
24
+ YAML = Doze::MediaType.register('application/yaml', :plus_suffix => 'yaml', :entity_class => Entity::YAML, :extension => 'yaml')
25
+ end
@@ -0,0 +1,220 @@
1
+ # Implements a subset of URI template spec.
2
+ # This is somewhat optimised for fast matching and generation of URI strings, although probably
3
+ # a fair bit of mileage still to be gotten out of it.
4
+ class Doze::URITemplate
5
+ def self.compile(string, var_regexps={})
6
+ is_varexp = true
7
+ parts = string.split(/\{(.*?)\}/).map do |bit|
8
+ if (is_varexp = !is_varexp)
9
+ case bit
10
+ when /^\/(.*).quadhexbytes\*$/
11
+ QuadHexBytesVariable.new($1.to_sym)
12
+ else
13
+ var = bit.to_sym
14
+ Variable.new(var, var_regexps[var] || Variable::DEFAULT_REGEXP)
15
+ end
16
+ else
17
+ String.new(bit)
18
+ end
19
+ end
20
+ template = parts.length > 1 ? Composite.new(parts) : parts.first
21
+ template.compile_expand!
22
+ template
23
+ end
24
+
25
+ # Compile a ruby string substitution expression for the 'expand' method to make filling out these templates blazing fast.
26
+ # This was actually a bottleneck in some simple cache lookups by list of URIs
27
+ def compile_expand!
28
+ instance_eval "def expand(vars); \"#{expand_code_fragment}\"; end", __FILE__, __LINE__
29
+ end
30
+
31
+ def anchored_regexp
32
+ @anchored_regexp ||= Regexp.new("^#{regexp_fragment}$")
33
+ end
34
+
35
+ def start_anchored_regexp
36
+ @start_anchored_regexp ||= Regexp.new("^#{regexp_fragment}")
37
+ end
38
+
39
+ def parts; @parts ||= [self]; end
40
+
41
+ def +(other)
42
+ other = String.new(other.to_s) unless other.is_a?(Doze::URITemplate)
43
+ Composite.new(parts + other.parts)
44
+ end
45
+
46
+ def inspect
47
+ "#<#{self.class} #{to_s}>"
48
+ end
49
+
50
+ def match(uri)
51
+ match = anchored_regexp.match(uri) or return
52
+ result = {}; captures = match.captures
53
+ variables.each_with_index do |var, index|
54
+ result[var.name] = var.translate_captured_string(captures[index])
55
+ end
56
+ result
57
+ end
58
+
59
+ def match_with_trailing(uri)
60
+ match = start_anchored_regexp.match(uri) or return
61
+ result = {}; captures = match.captures
62
+ variables.each_with_index do |var, index|
63
+ result[var.name] = var.translate_captured_string(captures[index])
64
+ end
65
+ trailing = match.post_match
66
+ trailing = nil if trailing.empty?
67
+ [result, match.to_s, trailing]
68
+ end
69
+
70
+ class Variable < Doze::URITemplate
71
+ DEFAULT_REGEXP = "[^\/.,;?]+"
72
+
73
+ attr_reader :name
74
+
75
+ def initialize(name, regexp=DEFAULT_REGEXP)
76
+ @name = name; @regexp = regexp
77
+ end
78
+
79
+ def regexp_fragment
80
+ "(#{@regexp})"
81
+ end
82
+
83
+ def to_s
84
+ "{#{@name}}"
85
+ end
86
+
87
+ def variables; [self]; end
88
+
89
+ def expand(vars)
90
+ Doze::Utils.escape(vars[@name].to_s)
91
+ end
92
+
93
+ def partially_expand(vars)
94
+ if vars.has_key?(@name)
95
+ String.new(expand(vars))
96
+ else
97
+ self
98
+ end
99
+ end
100
+
101
+ def translate_captured_string(string)
102
+ # inlines Doze::Utils.unescape, but with gsub! rather than gsub since this is faster and the matched string is throwaway
103
+ string.gsub!(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete('%')].pack('H*')}; string
104
+ end
105
+
106
+ # String#size under Ruby 1.8 and String#bytesize under 1.9.
107
+ BYTESIZE_METHOD = ''.respond_to?(:bytesize) ? 'bytesize' : 'size'
108
+
109
+ # inlines Doze::Utils.escape (optimised from Rack::Utils.escape) with further effort to avoid an extra method call for bytesize 1.9 compat.
110
+ def expand_code_fragment
111
+ "\#{vars[#{@name.inspect}].to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.#{BYTESIZE_METHOD}).join('%').upcase}}"
112
+ end
113
+ end
114
+
115
+ class QuadHexBytesVariable < Variable
116
+ REGEXP = "(?:/[0-9a-f]{2}){4}"
117
+
118
+ def initialize(name)
119
+ super(name, REGEXP)
120
+ end
121
+
122
+ def to_s
123
+ "{/#{@name}.quadhexbytes*}"
124
+ end
125
+
126
+ def expand(vars)
127
+ hex = vars[@name].to_i.to_s(16).rjust(8,'0')
128
+ "/#{hex[0..1]}/#{hex[2..3]}/#{hex[4..5]}/#{hex[6..7]}"
129
+ end
130
+
131
+ def expand_code_fragment
132
+ "/\#{hex=vars[#{@name.inspect}].to_i.to_s(16).rjust(8,'0');hex[0..1]}/\#{hex[2..3]}/\#{hex[4..5]}/\#{hex[6..7]}"
133
+ end
134
+
135
+ def translate_captured_string(string)
136
+ string.tr('/','').to_i(16)
137
+ end
138
+ end
139
+
140
+ class String < Doze::URITemplate
141
+ attr_reader :string
142
+
143
+ def initialize(string)
144
+ @string = string
145
+ end
146
+
147
+ def regexp_fragment
148
+ Regexp.escape(@string)
149
+ end
150
+
151
+ def to_s
152
+ @string
153
+ end
154
+
155
+ def expand(vars)
156
+ @string
157
+ end
158
+
159
+ def partially_expand(vars); self; end
160
+
161
+ NO_VARS = [].freeze
162
+ def variables; NO_VARS; end
163
+
164
+ def expand_code_fragment
165
+ @string.inspect[1...-1]
166
+ end
167
+ end
168
+
169
+ class Composite < Doze::URITemplate
170
+ def initialize(parts)
171
+ @parts = parts
172
+ end
173
+
174
+ def regexp_fragment
175
+ @parts.map {|p| p.regexp_fragment}.join
176
+ end
177
+
178
+ def to_s
179
+ @parts.join
180
+ end
181
+
182
+ def expand(vars)
183
+ @parts.map {|p| p.expand(vars)}.join
184
+ end
185
+
186
+ def partially_expand(vars)
187
+ Composite.new(@parts.map {|p| p.partially_expand(vars)})
188
+ end
189
+
190
+ def variables
191
+ @variables ||= @parts.map {|p| p.variables}.flatten
192
+ end
193
+
194
+ def expand_code_fragment
195
+ @parts.map {|p| p.expand_code_fragment}.join
196
+ end
197
+
198
+ attr_reader :parts
199
+ end
200
+
201
+ # A simple case of Composite where a template is prefixed by a string.
202
+ # This allows the same compiled URI template to be used with many different prefixes
203
+ # without having to re-compile the expand method for each of them, or use the slower
204
+ # default implementation
205
+ class WithPrefix < Composite
206
+ def initialize(template, prefix)
207
+ @template = template
208
+ @prefix = prefix
209
+ @parts = [String.new(prefix.to_s), *@template.parts]
210
+ end
211
+
212
+ def expand(vars)
213
+ "#{@prefix}#{@template.expand(vars)}"
214
+ end
215
+ end
216
+
217
+ def with_prefix(prefix)
218
+ WithPrefix.new(self, prefix)
219
+ end
220
+ end
data/lib/doze/utils.rb ADDED
@@ -0,0 +1,53 @@
1
+ # Various stateless utility functions which aid the conversion back and forth between HTTP syntax and the more abstracted ruby representations we use.
2
+ module Doze::Utils
3
+ # Strictly this is a WebDAV extension but very useful in the wider HTTP context
4
+ # see http://tools.ietf.org/html/rfc4918#section-11.2
5
+ Rack::Utils::HTTP_STATUS_CODES[422] = 'Unprocessable entity'
6
+
7
+ Rack::Utils::HTTP_STATUS_CODES.each do |code,text|
8
+ const_set('STATUS_' << text.upcase.gsub(/[^A-Z]+/, '_'), code)
9
+ end
10
+
11
+ URI_SCHEMES = Hash.new(URI::Generic).merge!(
12
+ 'http' => URI::HTTP,
13
+ 'https' => URI::HTTPS
14
+ )
15
+
16
+ def request_base_uri(request)
17
+ URI_SCHEMES[request.scheme].build(
18
+ :scheme => request.scheme,
19
+ :port => request.port,
20
+ :host => request.host
21
+ )
22
+ end
23
+
24
+ def quote(str)
25
+ '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
26
+ end
27
+
28
+ # Note: unescape and escape proved bottlenecks in URI template matching and URI template generation which in turn
29
+ # were bottlenecks for serving some simple requests and for generating URIs to use for cache lookups.
30
+ # So perhaps a bit micro-optimised here, but there was a reason :)
31
+
32
+ # Rack::Utils.unescape, but without turning '+' into ' '
33
+ # Also must be passed a string.
34
+ def unescape(s)
35
+ s.gsub(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete('%')].pack('H*')}
36
+ end
37
+
38
+ # Rack::Utils.escape, but turning ' ' into '%20' rather than '+' (which is not a necessary part of the URI spec) to save an extra call to tr.
39
+ # Also must be passed a string.
40
+ # Also avoids an extra call to 1.8/1.9 compatibility wrapper for bytesize/size.
41
+ if ''.respond_to?(:bytesize)
42
+ def escape(s)
43
+ s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.bytesize).join('%').upcase}
44
+ end
45
+ else
46
+ def escape(s)
47
+ s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.size).join('%').upcase}
48
+ end
49
+ end
50
+
51
+ # So utility functions are accessible as Doze::Utils.foo as well as via including the module
52
+ extend self
53
+ end
@@ -0,0 +1,3 @@
1
+ module Doze
2
+ VERSION = '0.0.11'
3
+ end
data/lib/doze.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'uri'
4
+ module Doze; end
5
+ require 'doze/application'
@@ -0,0 +1,69 @@
1
+ require 'functional/base'
2
+
3
+ class AuthTest < Test::Unit::TestCase
4
+ include Doze::Utils
5
+ include Doze::TestCase
6
+
7
+ def test_deny_unauthenticated_user
8
+ root.expects(:authorize).with(nil, :get).returns(false).once
9
+ assert_equal STATUS_UNAUTHORIZED, get.status
10
+ end
11
+
12
+ def test_deny_authenticated_user
13
+ root.expects(:authorize).with('username', :get).returns(false).once
14
+ get('REMOTE_USER' => 'username')
15
+ assert_equal STATUS_FORBIDDEN, last_response.status
16
+ end
17
+
18
+ def test_allow_authenticated_user
19
+ root.expects(:authorize).with('username', :get).returns(true).once
20
+ get('REMOTE_USER' => 'username')
21
+ assert_equal STATUS_OK, last_response.status
22
+ end
23
+
24
+ def test_post_auth
25
+ root.expects(:supports_post?).returns(true)
26
+ root.expects(:authorize).with('username', :post).returns(false).once
27
+ root.expects(:post).never
28
+ post('REMOTE_USER' => 'username')
29
+ assert_equal STATUS_FORBIDDEN, last_response.status
30
+ end
31
+
32
+ def test_put_auth
33
+ root.expects(:supports_put?).returns(true)
34
+ root.expects(:authorize).with('username', :put).returns(false).once
35
+ root.expects(:put).never
36
+ put('REMOTE_USER' => 'username')
37
+ assert_equal STATUS_FORBIDDEN, last_response.status
38
+ end
39
+
40
+ def test_delete_auth
41
+ root.expects(:supports_delete?).returns(true)
42
+ root.expects(:authorize).with('username', :delete).returns(false).once
43
+ root.expects(:delete).never
44
+ delete('REMOTE_USER' => 'username')
45
+ assert_equal STATUS_FORBIDDEN, last_response.status
46
+ end
47
+
48
+ def test_other_method_auth
49
+ app(:recognized_methods => [:get, :patch])
50
+ root.expects(:supports_patch?).returns(true)
51
+ root.expects(:authorize).with('username', :patch).returns(false).once
52
+ root.expects(:patch).never
53
+ other_request_method('PATCH', {'REMOTE_USER' => 'username'})
54
+ assert_equal STATUS_FORBIDDEN, last_response.status
55
+ end
56
+
57
+ def test_can_raise_unauthd_errors
58
+ root.expects(:get).raises(Doze::UnauthorizedError.new('no homers allowed'))
59
+ assert_equal STATUS_UNAUTHORIZED, get.status
60
+ assert_match /Unauthorized\: no homers allowed/, last_response.body
61
+ end
62
+
63
+ def test_can_raise_forbidden_errors
64
+ root.expects(:get).raises(Doze::ForbiddenError.new('do not go there, girlfriend'))
65
+ assert_equal STATUS_FORBIDDEN, get.status
66
+ assert_match /Forbidden\: do not go there, girlfriend/, last_response.body
67
+ end
68
+
69
+ end