doze 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|