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.
- data/README +6 -0
- data/lib/doze/application.rb +92 -0
- data/lib/doze/collection/object.rb +14 -0
- data/lib/doze/entity.rb +62 -0
- data/lib/doze/error.rb +75 -0
- data/lib/doze/media_type.rb +135 -0
- data/lib/doze/negotiator.rb +107 -0
- data/lib/doze/request.rb +119 -0
- data/lib/doze/resource/error.rb +21 -0
- data/lib/doze/resource/proxy.rb +81 -0
- data/lib/doze/resource.rb +193 -0
- data/lib/doze/responder/error.rb +34 -0
- data/lib/doze/responder/main.rb +41 -0
- data/lib/doze/responder/resource.rb +262 -0
- data/lib/doze/responder.rb +58 -0
- data/lib/doze/response.rb +78 -0
- data/lib/doze/router/anchored_route_set.rb +68 -0
- data/lib/doze/router/route.rb +88 -0
- data/lib/doze/router/route_set.rb +34 -0
- data/lib/doze/router.rb +100 -0
- data/lib/doze/serialization/entity.rb +34 -0
- data/lib/doze/serialization/form_data_helpers.rb +40 -0
- data/lib/doze/serialization/html.rb +116 -0
- data/lib/doze/serialization/json.rb +29 -0
- data/lib/doze/serialization/multipart_form_data.rb +162 -0
- data/lib/doze/serialization/resource.rb +30 -0
- data/lib/doze/serialization/resource_proxy.rb +14 -0
- data/lib/doze/serialization/www_form_encoded.rb +42 -0
- data/lib/doze/serialization/yaml.rb +25 -0
- data/lib/doze/uri_template.rb +220 -0
- data/lib/doze/utils.rb +53 -0
- data/lib/doze/version.rb +3 -0
- data/lib/doze.rb +5 -0
- data/test/functional/auth_test.rb +69 -0
- data/test/functional/base.rb +159 -0
- data/test/functional/cache_header_test.rb +76 -0
- data/test/functional/direct_response_test.rb +16 -0
- data/test/functional/error_handling_test.rb +131 -0
- data/test/functional/get_and_conneg_test.rb +182 -0
- data/test/functional/media_type_extensions_test.rb +102 -0
- data/test/functional/media_type_test.rb +40 -0
- data/test/functional/method_support_test.rb +49 -0
- data/test/functional/non_get_method_test.rb +173 -0
- data/test/functional/precondition_test.rb +84 -0
- data/test/functional/raw_path_info_test.rb +69 -0
- data/test/functional/resource_representation_test.rb +14 -0
- data/test/functional/router_test.rb +196 -0
- data/test/functional/serialization_test.rb +142 -0
- data/test/functional/uri_template_test.rb +51 -0
- metadata +221 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'doze'
|
2
|
+
require 'doze/media_type'
|
3
|
+
|
4
|
+
# works around this error when newer test-unit gem present:
|
5
|
+
# http://stackoverflow.com/questions/1145318/getting-uninitialized-constant-error-when-trying-to-run-tests
|
6
|
+
begin; gem 'test-unit'; rescue NoMethodError, LoadError; end
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'rack/test'
|
10
|
+
require 'mocha/setup'
|
11
|
+
|
12
|
+
class Doze::MockResource
|
13
|
+
include Doze::Resource
|
14
|
+
|
15
|
+
attr_reader :extra_params, :data
|
16
|
+
|
17
|
+
def initialize(uri=nil, binary_data='')
|
18
|
+
@uri = uri
|
19
|
+
@binary_data = binary_data
|
20
|
+
end
|
21
|
+
|
22
|
+
def get
|
23
|
+
Doze::Entity.new(Doze::MediaType['text/html'], :binary_data => @binary_data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Doze::TestCase
|
28
|
+
include Rack::Test::Methods
|
29
|
+
|
30
|
+
TEST_CONFIG = {
|
31
|
+
:catch_application_errors => false,
|
32
|
+
:session_from_rack_env => proc {|env| env['REMOTE_USER']},
|
33
|
+
:session_authenticated => proc {|session| !session.nil?}
|
34
|
+
}
|
35
|
+
|
36
|
+
def app(config={})
|
37
|
+
@app ||= Doze::Application.new(root, TEST_CONFIG.merge(config))
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_writer :root
|
41
|
+
|
42
|
+
def root
|
43
|
+
@root ||= Doze::MockResource.new("/")
|
44
|
+
end
|
45
|
+
|
46
|
+
def root_router(&b)
|
47
|
+
@root ||= mock_router(&b)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(path='/', env={}, &b)
|
51
|
+
path, env = '/', path if path.is_a?(Hash)
|
52
|
+
super(path, {}, env, &b)
|
53
|
+
end
|
54
|
+
|
55
|
+
def put(path='/', env={}, &b)
|
56
|
+
path, env = '/', path if path.is_a?(Hash)
|
57
|
+
super(path, {}, env, &b)
|
58
|
+
end
|
59
|
+
|
60
|
+
def post(path='/', env={}, &b)
|
61
|
+
path, env = '/', path if path.is_a?(Hash)
|
62
|
+
super(path, {}, env, &b)
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(path='/', env={}, &b)
|
66
|
+
path, env = '/', path if path.is_a?(Hash)
|
67
|
+
super(path, {}, env, &b)
|
68
|
+
end
|
69
|
+
|
70
|
+
def head(path='/', env={}, &b)
|
71
|
+
path, env = '/', path if path.is_a?(Hash)
|
72
|
+
super(path, {}, env, &b)
|
73
|
+
end
|
74
|
+
|
75
|
+
def other_request_method(method, path='/', env={}, &block)
|
76
|
+
path, env = '/', path if path.is_a?(Hash)
|
77
|
+
request(path, env.merge(:method => method, :params => {}), &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def mock_entity(binary_data, media_type='text/html', language=nil)
|
81
|
+
media_type = Doze::MediaType[media_type] if media_type.is_a?(String)
|
82
|
+
Doze::Entity.new(media_type, :binary_data => binary_data, :language => language)
|
83
|
+
end
|
84
|
+
|
85
|
+
def mock_resource(*p); Doze::MockResource.new(*p); end
|
86
|
+
|
87
|
+
def mock_router(superclass = Object, &block)
|
88
|
+
klass = Class.new(superclass)
|
89
|
+
klass.send(:include, Doze::Router)
|
90
|
+
klass.class_eval(&block) if block
|
91
|
+
klass.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def assert_response_header(header, value, message=nil)
|
95
|
+
assert_block(build_message(message, "<?> was expected for last response header <?>", value, header)) do
|
96
|
+
r = last_response and v = r.headers[header] and v == value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_response_header_includes(header, value, message=nil)
|
101
|
+
assert_block(build_message(message, "<?> was expected in last response header <?>", value, header)) do
|
102
|
+
r = last_response and v = r.headers[header] and v.split(/,\s+/).include?(value)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def assert_response_header_not_includes(header, value, message=nil)
|
107
|
+
assert_block(build_message(message, "<?> was not expected in last response header <?>", value, header)) do
|
108
|
+
!(r = last_response and v = r.headers[header] and v.split(/,\s+/).include?(value))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def assert_response_header_exists(header, message=nil)
|
113
|
+
assert_block(build_message(message, "<?> response header was expected", header)) do
|
114
|
+
r = last_response and !r.headers[header].nil?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def assert_no_response_header(header, message=nil)
|
119
|
+
assert_block(build_message(message, "<?> response header was not expected", header)) do
|
120
|
+
r = last_response and r.headers[header].nil?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
# To be used for any test that defines new media types - cleans them up afterwards in the registry
|
127
|
+
module Doze::MediaTypeTestCase
|
128
|
+
def setup
|
129
|
+
@media_type_name_lookup = Doze::MediaType::NAME_LOOKUP.dup
|
130
|
+
@media_type_by_extension = Doze::MediaType::BY_EXTENSION.dup
|
131
|
+
super
|
132
|
+
end
|
133
|
+
|
134
|
+
def teardown
|
135
|
+
super
|
136
|
+
Doze::MediaType::NAME_LOOKUP.replace(@media_type_name_lookup)
|
137
|
+
Doze::MediaType::BY_EXTENSION.replace(@media_type_by_extension)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Rack::MockResponse
|
142
|
+
|
143
|
+
# Useful to have these helpers in MockResponse corresponding to those in request:
|
144
|
+
|
145
|
+
def media_type
|
146
|
+
content_type && content_type.split(/\s*[;,]\s*/, 2)[0].downcase
|
147
|
+
end
|
148
|
+
|
149
|
+
def media_type_params
|
150
|
+
return {} if content_type.nil?
|
151
|
+
content_type.split(/\s*[;,]\s*/)[1..-1].
|
152
|
+
collect { |s| s.split('=', 2) }.
|
153
|
+
inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
|
154
|
+
end
|
155
|
+
|
156
|
+
def content_charset
|
157
|
+
media_type_params['charset']
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'functional/base'
|
2
|
+
|
3
|
+
class CacheHeaderTest < Test::Unit::TestCase
|
4
|
+
include Doze::Utils
|
5
|
+
include Doze::TestCase
|
6
|
+
|
7
|
+
def test_with_no_cache_headers
|
8
|
+
root.expects(:cacheable?).returns(nil).once
|
9
|
+
get
|
10
|
+
assert_nil last_response.headers['Cache-Control']
|
11
|
+
assert_nil last_response.headers['Expires']
|
12
|
+
assert_response_header_exists 'Etag'
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_not_cacheable
|
16
|
+
root.expects(:cacheable?).returns(false).once
|
17
|
+
get
|
18
|
+
assert_response_header_includes 'Cache-Control', 'no-cache'
|
19
|
+
assert_response_header_includes 'Cache-Control', 'max-age=0'
|
20
|
+
assert Time.httpdate(last_response.headers['Expires']) < Time.now
|
21
|
+
assert_no_response_header 'Last-Modified'
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_cacheable_but_no_expiry
|
25
|
+
root.expects(:cacheable?).returns(true).at_least_once
|
26
|
+
get
|
27
|
+
assert_response_header_not_includes 'Cache-Control', 'no-cache'
|
28
|
+
assert_response_header_not_includes 'Cache-Control', 'max-age'
|
29
|
+
assert_response_header_includes 'Cache-Control', 'public'
|
30
|
+
assert_nil last_response.headers['Expires']
|
31
|
+
assert_response_header_exists 'Etag'
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_cacheable_with_expiry
|
35
|
+
root.expects(:cacheable?).returns(true).at_least_once
|
36
|
+
root.expects(:cache_expiry_period).returns(60).once
|
37
|
+
get
|
38
|
+
assert_response_header_includes 'Cache-Control', 'max-age=60'
|
39
|
+
assert_response_header_includes 'Cache-Control', 'public'
|
40
|
+
assert_in_delta Time.now+60, Time.httpdate(last_response.headers['Expires']), 1
|
41
|
+
assert_response_header_exists 'Etag'
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_last_modified
|
45
|
+
while_ago = Time.now - 30
|
46
|
+
root.expects(:last_modified).returns(while_ago).at_least_once
|
47
|
+
get
|
48
|
+
assert_response_header 'Last-Modified', while_ago.httpdate
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_private_cacheable
|
52
|
+
root.expects(:cacheable?).returns(true).once
|
53
|
+
root.expects(:publicly_cacheable?).returns(false).once
|
54
|
+
root.expects(:cache_expiry_period).returns(60).once
|
55
|
+
get
|
56
|
+
assert_response_header_includes 'Cache-Control', 'max-age=60'
|
57
|
+
assert_response_header_not_includes 'Cache-Control', 's-maxage'
|
58
|
+
assert_response_header_not_includes 'Cache-Control', 'public'
|
59
|
+
assert_response_header_includes 'Cache-Control', 'private'
|
60
|
+
assert_in_delta Time.now+60, Time.httpdate(last_response.headers['Expires']), 1
|
61
|
+
assert_response_header_exists 'Etag'
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_cacheable_with_different_public_private_expiry
|
65
|
+
root.expects(:cacheable?).returns(true).at_least_once
|
66
|
+
root.expects(:cache_expiry_period).returns(60).once
|
67
|
+
root.expects(:public_cache_expiry_period).returns(30).once
|
68
|
+
get
|
69
|
+
assert_response_header_includes 'Cache-Control', 'max-age=60'
|
70
|
+
assert_response_header_includes 'Cache-Control', 's-maxage=30'
|
71
|
+
assert_response_header_includes 'Cache-Control', 'public'
|
72
|
+
assert_response_header_not_includes 'Cache-Control', 'private'
|
73
|
+
assert_in_delta Time.now+60, Time.httpdate(last_response.headers['Expires']), 1
|
74
|
+
assert_response_header_exists 'Etag'
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'functional/base'
|
2
|
+
|
3
|
+
class DirectResponseTest < Test::Unit::TestCase
|
4
|
+
include Doze::Utils
|
5
|
+
include Doze::TestCase
|
6
|
+
|
7
|
+
def test_get_with_direct_response
|
8
|
+
resource = mock_resource
|
9
|
+
response = Doze::Response.new(456, {"X-Foo" => "Bar"}, "Yadda!")
|
10
|
+
root.expects(:get).returns(response)
|
11
|
+
get
|
12
|
+
assert_equal 456, last_response.status
|
13
|
+
assert_equal "Yadda!", last_response.body
|
14
|
+
assert_response_header 'X-Foo', 'Bar'
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'functional/base'
|
2
|
+
|
3
|
+
class CustomErrorResource
|
4
|
+
include Doze::Resource
|
5
|
+
def initialize(status, message, extras={})
|
6
|
+
@status = status; @message = message
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class FooException < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
class ErrorHandlingTest < Test::Unit::TestCase
|
14
|
+
include Doze::Utils
|
15
|
+
include Doze::TestCase
|
16
|
+
|
17
|
+
def test_default_error_resource
|
18
|
+
root.expects(:exists?).returns(false).at_least_once
|
19
|
+
get
|
20
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
21
|
+
assert_match /not found/i, last_response.body
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_default_error_resource_negotiation
|
25
|
+
root.expects(:exists?).returns(false).at_least_once
|
26
|
+
get('HTTP_ACCEPT' => 'application/json')
|
27
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
28
|
+
assert_equal 'application/json', last_response.media_type
|
29
|
+
|
30
|
+
get('HTTP_ACCEPT' => 'application/yaml')
|
31
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
32
|
+
assert_equal 'application/json', last_response.media_type
|
33
|
+
|
34
|
+
# Failure of negotiation should be ignored for an error resource, not result in a STATUS_NOT_ACCEPTABLE
|
35
|
+
get('HTTP_ACCEPT' => 'application/bollocks')
|
36
|
+
assert_not_equal STATUS_NOT_ACCEPTABLE, last_response.status
|
37
|
+
assert_match /not found/i, last_response.body
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_custom_error_resource
|
41
|
+
e = CustomErrorResource.new(STATUS_NOT_FOUND, 'Not Found')
|
42
|
+
CustomErrorResource.expects(:new).with(STATUS_NOT_FOUND, 'Not Found', anything).returns(e).once
|
43
|
+
entity = Doze::Entity.new(Doze::MediaType['text/html'], :binary_data => "foo bar baz")
|
44
|
+
e.expects(:get).returns(entity).once
|
45
|
+
|
46
|
+
root.expects(:exists?).returns(false).at_least_once
|
47
|
+
|
48
|
+
app(:error_resource_class => CustomErrorResource)
|
49
|
+
get
|
50
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
51
|
+
assert_equal 'text/html', last_response.media_type
|
52
|
+
assert_equal "foo bar baz", last_response.body
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_custom_error_resource_can_access_original_error
|
56
|
+
e = CustomErrorResource.new(STATUS_NOT_FOUND, 'Not Found: Oi!')
|
57
|
+
|
58
|
+
CustomErrorResource.expects(:new).with(STATUS_NOT_FOUND, 'Not Found: Oi!', has_key(:error)).returns(e).once
|
59
|
+
entity = Doze::Entity.new(Doze::MediaType['text/html'], :binary_data => "foo bar baz")
|
60
|
+
e.expects(:get).returns(entity).once
|
61
|
+
|
62
|
+
|
63
|
+
app(:error_resource_class => CustomErrorResource)
|
64
|
+
root.expects(:get).raises(Doze::ResourceNotFoundError, 'Oi!')
|
65
|
+
|
66
|
+
get
|
67
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
68
|
+
assert_equal 'text/html', last_response.media_type
|
69
|
+
assert_equal "foo bar baz", last_response.body
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_error_without_error_resource
|
73
|
+
root.expects(:exists?).returns(false).at_least_once
|
74
|
+
app(:error_resource_class => nil)
|
75
|
+
get
|
76
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
77
|
+
assert_equal 'text/plain', last_response.media_type
|
78
|
+
assert_match /not found/i, last_response.body
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_exception_caught_from_resource_code
|
82
|
+
app(:error_resource_class => Doze::Resource::Error, :catch_application_errors => true)
|
83
|
+
|
84
|
+
root.expects(:get).raises(RuntimeError, 'Oi!')
|
85
|
+
get
|
86
|
+
assert_equal STATUS_INTERNAL_SERVER_ERROR, last_response.status
|
87
|
+
assert_match /internal server error/i, last_response.body
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_exception_not_caught_from_resource_code
|
91
|
+
app(:error_resource_class => Doze::Resource::Error, :catch_application_errors => false)
|
92
|
+
|
93
|
+
root.expects(:get).raises(FooException, 'Oi!')
|
94
|
+
assert_raise(FooException) {get}
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_exception_within_error_resource_code
|
98
|
+
app(:error_resource_class => CustomErrorResource, :catch_application_errors => true)
|
99
|
+
|
100
|
+
CustomErrorResource.any_instance.expects(:get).at_least_once.raises(FooException)
|
101
|
+
|
102
|
+
root.expects(:exists?).returns(false).at_least_once
|
103
|
+
get
|
104
|
+
assert_equal STATUS_INTERNAL_SERVER_ERROR, last_response.status
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_exception_within_error_resource_code_after_catching_exception_from_resource_code
|
108
|
+
app(:error_resource_class => CustomErrorResource, :catch_application_errors => true)
|
109
|
+
|
110
|
+
root.expects(:get).raises(RuntimeError)
|
111
|
+
CustomErrorResource.any_instance.expects(:get).raises(FooException)
|
112
|
+
|
113
|
+
get
|
114
|
+
assert_equal STATUS_INTERNAL_SERVER_ERROR, last_response.status
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_get_unavailable_resource
|
118
|
+
root.expects(:get).raises(Doze::ResourceUnavailableError.new)
|
119
|
+
|
120
|
+
get
|
121
|
+
assert_equal STATUS_SERVICE_UNAVAILABLE, last_response.status
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_not_found_error
|
125
|
+
root.expects(:get).raises(Doze::ResourceNotFoundError.new)
|
126
|
+
|
127
|
+
get
|
128
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'functional/base'
|
2
|
+
|
3
|
+
class GetAndConnegTest < Test::Unit::TestCase
|
4
|
+
include Doze::Utils
|
5
|
+
include Doze::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@entities = [
|
9
|
+
mock_entity('<foo>Yalrightmate</foo>', 'text/html', 'en-gb'),
|
10
|
+
mock_entity("{foo: 'Yalrightmate'}", 'application/json', 'en-gb'),
|
11
|
+
mock_entity("--- \nfoo: Yalrightmate\n", 'application/yaml', 'en-gb'),
|
12
|
+
|
13
|
+
mock_entity('<foo>Wassup</foo>', 'text/html', 'en-us'),
|
14
|
+
mock_entity("{foo: 'Wassup'}", 'application/json', 'en-us'),
|
15
|
+
mock_entity("--- \nfoo: Wassup\n", 'application/yaml', 'en-us'),
|
16
|
+
|
17
|
+
mock_entity("<foo>Wie geht's</foo>", 'text/html', 'de'),
|
18
|
+
mock_entity('{"foo": "Wie geht\'s"}', 'application/json', 'de')
|
19
|
+
# no yaml in german
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_get
|
24
|
+
root.expects(:get).returns(mock_entity('foo', 'text/html')).once
|
25
|
+
assert_equal STATUS_OK, get.status
|
26
|
+
assert_equal 'foo', last_response.body
|
27
|
+
assert_equal 'text/html', last_response.media_type
|
28
|
+
assert_not_nil last_response.headers['Date']
|
29
|
+
assert_in_delta Time.now, Time.httpdate(last_response.headers['Date']), 1
|
30
|
+
assert_equal 'foo'.length, last_response.headers['Content-Length'].to_i
|
31
|
+
assert_nil last_response.headers['Content-Language']
|
32
|
+
assert !(last_response.headers['Vary'] || '').split(/,\s*/).include?('Accept')
|
33
|
+
assert !(last_response.headers['Vary'] || '').split(/,\s*/).include?('Accept-Language')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_not_exists_get
|
37
|
+
root.expects(:exists?).returns(false).once
|
38
|
+
root.expects(:get).never
|
39
|
+
assert_equal STATUS_NOT_FOUND, get.status
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_with_media_type_variation_and_no_accept
|
43
|
+
root.expects(:get).returns([
|
44
|
+
mock_entity('<foo>fdgfdgfd</foo>', 'text/html'),
|
45
|
+
mock_entity("{foo: 'fdgfdgfd'}", 'application/json'),
|
46
|
+
]).once
|
47
|
+
assert_equal STATUS_OK, get.status
|
48
|
+
assert_equal "<foo>fdgfdgfd</foo>", last_response.body
|
49
|
+
assert_equal 'text/html', last_response.media_type
|
50
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept')
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_get_with_multiple_entities_and_no_media_type_variation
|
54
|
+
root.expects(:get).returns([
|
55
|
+
mock_entity('<foo>fdgfdgfd</foo>', 'text/html'),
|
56
|
+
mock_entity('<foo>Yalrightmate</foo>', 'text/html')
|
57
|
+
]).once
|
58
|
+
assert_equal STATUS_OK, get.status
|
59
|
+
assert !(last_response.headers['Vary'] || '').split(/,\s*/).include?('Accept')
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_binary_data_never_called_on_undesired_entity
|
63
|
+
root.expects(:get).returns(@entities).once
|
64
|
+
@entities[0].expects(:binary_data).returns('<foo>Yalrightmate</foo>').at_least_once
|
65
|
+
@entities[1].expects(:binary_data).never
|
66
|
+
@entities[2].expects(:binary_data).never
|
67
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'text/html').status
|
68
|
+
end
|
69
|
+
|
70
|
+
# todo break this up a bit
|
71
|
+
def test_get_with_media_type_negotiation_and_various_accept
|
72
|
+
root.expects(:get).returns(@entities).at_least_once
|
73
|
+
|
74
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'application/json').status
|
75
|
+
assert_equal "{foo: 'Yalrightmate'}", last_response.body
|
76
|
+
assert_equal 'application/json', last_response.media_type
|
77
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept')
|
78
|
+
|
79
|
+
assert_equal STATUS_NOT_ACCEPTABLE, get('HTTP_ACCEPT' => '*/*; q=0').status
|
80
|
+
assert_equal STATUS_NOT_ACCEPTABLE, get('HTTP_ACCEPT' => 'application/bollocks; q=1').status
|
81
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'text/*').status
|
82
|
+
assert_equal 'text/html', last_response.media_type
|
83
|
+
# orders by q-value, default of 1
|
84
|
+
assert_equal 'application/json', get('HTTP_ACCEPT' => 'application/json, text/html; q=0.5').media_type
|
85
|
+
assert_equal 'application/json', get('HTTP_ACCEPT' => 'application/json; q=0.8, text/html; q=0.5').media_type
|
86
|
+
assert_equal 'text/html', get('HTTP_ACCEPT' => 'application/json; q=0.4, text/html; q=0.5').media_type
|
87
|
+
# text/html is more specific than text/*
|
88
|
+
assert_equal STATUS_NOT_ACCEPTABLE, get('HTTP_ACCEPT' => 'text/*; q=0').status
|
89
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'text/*; q=0, text/html; q=1').status
|
90
|
+
|
91
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'text/*; q=0, */*; q=1').status
|
92
|
+
assert_not_equal 'text/html', last_response.media_type
|
93
|
+
|
94
|
+
# doesn't just look for the highest q-value that matches, but uses the matching rules and their specificities of matching to
|
95
|
+
# construct a media_type => q-value mapping and then uses that to prioritize what's available:
|
96
|
+
assert_equal 'application/yaml', get('HTTP_ACCEPT' => 'application/*; q=0.6, application/json; q=0.4').media_type
|
97
|
+
assert_equal 'application/json', get('HTTP_ACCEPT' => 'application/*; q=0.6, application/yaml; q=0.4').media_type
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_get_with_language_variation_and_no_accept
|
101
|
+
root.expects(:get).returns([
|
102
|
+
mock_entity('<foo>Yalrightmate</foo>', 'text/html', 'en-gb'),
|
103
|
+
mock_entity('<foo>Wassup</foo>', 'text/html', 'en-us'),
|
104
|
+
]).once
|
105
|
+
|
106
|
+
assert_equal STATUS_OK, get.status
|
107
|
+
assert_equal '<foo>Yalrightmate</foo>', last_response.body
|
108
|
+
assert_equal 'text/html', last_response.media_type
|
109
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
110
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept-Language')
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_get_with_non_language_entity
|
114
|
+
entity = mock_entity('{"1234": "5678"}', 'application/json', nil)
|
115
|
+
root.expects(:get).returns([entity]).once
|
116
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT_LANGUAGE' => 'en-gb').status
|
117
|
+
assert_equal nil, last_response.headers['Content-Language']
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_get_with_language_and_non_language_entity
|
121
|
+
# no language preference is specified - should not prefer language over non-language, over other criterea
|
122
|
+
root.expects(:get).returns([
|
123
|
+
mock_entity('{"1234": "5678"}', 'application/json', 'en-gb'),
|
124
|
+
mock_entity('<foo></foo>', 'text/html', nil)
|
125
|
+
]).once
|
126
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT' => 'text/html; q=1, application/json; q=0.5').status
|
127
|
+
assert_equal 'text/html', last_response.media_type
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_get_with_multiple_entities_and_no_language_variation
|
131
|
+
root.expects(:get).returns([@entities[0], @entities[1]]).once
|
132
|
+
assert_equal STATUS_OK, get.status
|
133
|
+
assert !last_response.headers['Vary'].split(/,\s*/).include?('Accept-Language')
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_get_with_language_negotiation_and_accept_language
|
137
|
+
root.expects(:get).returns(@entities).once
|
138
|
+
|
139
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT_LANGUAGE' => 'en-gb').status
|
140
|
+
assert_equal "<foo>Yalrightmate</foo>", last_response.body
|
141
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
142
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept-Language')
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_get_with_language_negotiation_and_various_accept_language
|
146
|
+
root.expects(:get).returns(@entities).at_least_once
|
147
|
+
|
148
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT_LANGUAGE' => 'en-gb').status
|
149
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
150
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT_LANGUAGE' => 'en').status
|
151
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
152
|
+
assert_equal STATUS_NOT_ACCEPTABLE, get('HTTP_ACCEPT_LANGUAGE' => 'en-au').status
|
153
|
+
|
154
|
+
assert_equal 'en-gb', get('HTTP_ACCEPT_LANGUAGE' => 'en-gb; q=0.7, en; q=0.5').headers['Content-Language']
|
155
|
+
assert_equal 'en-us', get('HTTP_ACCEPT_LANGUAGE' => 'en-us; q=0.7, en; q=0.5').headers['Content-Language']
|
156
|
+
assert_equal 'en-us', get('HTTP_ACCEPT_LANGUAGE' => 'en-gb; q=0.4, en; q=0.5').headers['Content-Language']
|
157
|
+
assert_equal 'en-gb', get('HTTP_ACCEPT_LANGUAGE' => 'en-us; q=0.4, en; q=0.5').headers['Content-Language']
|
158
|
+
|
159
|
+
assert_equal 'de', get('HTTP_ACCEPT_LANGUAGE' => 'de; q=0.9, *; q=0.5').headers['Content-Language']
|
160
|
+
assert_equal 'de', get('HTTP_ACCEPT_LANGUAGE' => '*; q=0.5, de').headers['Content-Language']
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_get_with_both_negotiation
|
164
|
+
root.expects(:get).returns(@entities).at_least_once
|
165
|
+
|
166
|
+
assert_equal STATUS_OK, get('HTTP_ACCEPT_LANGUAGE' => 'en-gb', 'HTTP_ACCEPT' => 'text/html').status
|
167
|
+
assert_equal "<foo>Yalrightmate</foo>", last_response.body
|
168
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
169
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept-Language')
|
170
|
+
assert last_response.headers['Vary'].split(/,\s*/).include?('Accept')
|
171
|
+
|
172
|
+
# check that q-values are multiplied to get a combined q-value. This relies on the fact that a german yaml version doesn't exist,
|
173
|
+
# so do we prioritise the preferred language over the preferred media type:
|
174
|
+
get('HTTP_ACCEPT_LANGUAGE' => 'de; q=0.9, en; q=0.1', 'HTTP_ACCEPT' => 'application/yaml; q=0.6, text/html; q=0.5')
|
175
|
+
assert_equal 'de', last_response.headers['Content-Language']
|
176
|
+
assert_equal 'text/html', last_response.media_type
|
177
|
+
# or vice versa:
|
178
|
+
get('HTTP_ACCEPT_LANGUAGE' => 'de; q=0.6, en; q=0.5', 'HTTP_ACCEPT' => 'application/yaml; q=0.9, text/html; q=0.1')
|
179
|
+
assert_equal 'en-gb', last_response.headers['Content-Language']
|
180
|
+
assert_equal 'application/yaml', last_response.media_type
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'functional/base'
|
2
|
+
require 'doze/application'
|
3
|
+
|
4
|
+
class MediaTypeExtensionsTest < Test::Unit::TestCase
|
5
|
+
include Doze::Utils
|
6
|
+
include Doze::TestCase
|
7
|
+
include Doze::MediaTypeTestCase
|
8
|
+
|
9
|
+
def test_get
|
10
|
+
app(:media_type_extensions => true)
|
11
|
+
|
12
|
+
foo_mt = Doze::MediaType.register('application/foo', :extension => 'foo')
|
13
|
+
bar_mt = Doze::MediaType.register('application/bar', :extension => 'bar')
|
14
|
+
baz_mt = Doze::MediaType.register('application/baz', :extension => 'baz')
|
15
|
+
|
16
|
+
entities = [mock_entity('foo', foo_mt), mock_entity('bar', bar_mt)]
|
17
|
+
root.expects(:get).returns(entities).at_least_once
|
18
|
+
|
19
|
+
get "/.foo"
|
20
|
+
assert_equal "foo", last_response.body
|
21
|
+
assert_equal "application/foo", last_response.media_type
|
22
|
+
|
23
|
+
get "/.bar"
|
24
|
+
assert_equal "bar", last_response.body
|
25
|
+
assert_equal "application/bar", last_response.media_type
|
26
|
+
|
27
|
+
get "/.baz"
|
28
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
29
|
+
|
30
|
+
get "/.nob"
|
31
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
32
|
+
end
|
33
|
+
|
34
|
+
def PENDING_test_put_with_different_supported_media_type
|
35
|
+
app(:media_type_extensions => true)
|
36
|
+
|
37
|
+
foo_mt = Doze::MediaType.register('application/foo', :extension => 'foo')
|
38
|
+
bar_mt = Doze::MediaType.register('application/bar', :extension => 'bar')
|
39
|
+
|
40
|
+
root.expects(:supports_put?).returns(true)
|
41
|
+
root.expects(:put).never
|
42
|
+
root.stubs(:accepts_method_with_media_type? => true)
|
43
|
+
|
44
|
+
put "/.bar", :input => 'foo', 'CONTENT_TYPE' => 'application/foo'
|
45
|
+
assert_equal STATUS_UNSUPPORTED_MEDIA_TYPE, last_response.status
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_put_with_same_supported_media_type
|
49
|
+
app(:media_type_extensions => true)
|
50
|
+
|
51
|
+
foo_mt = Doze::MediaType.register('application/foo', :extension => 'foo')
|
52
|
+
bar_mt = Doze::MediaType.register('application/bar', :extension => 'bar')
|
53
|
+
|
54
|
+
root.expects(:supports_put?).returns(true)
|
55
|
+
root.expects(:accepts_method_with_media_type?).with(:put, anything).returns(true)
|
56
|
+
root.expects(:put).with {|e| e.media_type.name == 'application/foo'}
|
57
|
+
|
58
|
+
put "/.foo", :input => 'foo', 'CONTENT_TYPE' => 'application/foo'
|
59
|
+
assert last_response.successful?
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_post
|
63
|
+
app(:media_type_extensions => true)
|
64
|
+
|
65
|
+
foo_mt = Doze::MediaType.register('application/foo', :extension => 'foo')
|
66
|
+
bar_mt = Doze::MediaType.register('application/bar', :extension => 'bar')
|
67
|
+
|
68
|
+
result = mock_resource
|
69
|
+
entities = [mock_entity('foo', foo_mt), mock_entity('bar', bar_mt)]
|
70
|
+
result.expects(:get).returns(entities)
|
71
|
+
|
72
|
+
root.expects(:supports_post?).returns(true)
|
73
|
+
root.expects(:accepts_method_with_media_type?).with(:post, anything).returns(true)
|
74
|
+
root.expects(:post).returns(result)
|
75
|
+
|
76
|
+
post "/.bar", :input => 'foo', 'CONTENT_TYPE' => 'application/foo'
|
77
|
+
assert last_response.successful?
|
78
|
+
assert_equal "bar", last_response.body
|
79
|
+
assert_equal "application/bar", last_response.media_type
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_affects_representation_selected_for_error_resources
|
83
|
+
app(:catch_application_errors => true, :media_type_extensions => true)
|
84
|
+
|
85
|
+
root.expects(:get).raises(RuntimeError, "test error")
|
86
|
+
|
87
|
+
get("/.json")
|
88
|
+
assert_equal STATUS_INTERNAL_SERVER_ERROR, last_response.status
|
89
|
+
assert_equal 'application/json', last_response.media_type
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_affects_representation_of_not_found_error_when_requested_media_type_not_available
|
93
|
+
app(:media_type_extensions => true)
|
94
|
+
|
95
|
+
root.expects(:get).returns([mock_entity('foo', 'text/html')])
|
96
|
+
|
97
|
+
get("/.json")
|
98
|
+
assert_equal STATUS_NOT_FOUND, last_response.status
|
99
|
+
assert_equal 'application/json', last_response.media_type
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|