doze 0.0.11

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