doze 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/README +6 -0
  2. data/lib/doze/application.rb +92 -0
  3. data/lib/doze/collection/object.rb +14 -0
  4. data/lib/doze/entity.rb +62 -0
  5. data/lib/doze/error.rb +75 -0
  6. data/lib/doze/media_type.rb +135 -0
  7. data/lib/doze/negotiator.rb +107 -0
  8. data/lib/doze/request.rb +119 -0
  9. data/lib/doze/resource/error.rb +21 -0
  10. data/lib/doze/resource/proxy.rb +81 -0
  11. data/lib/doze/resource.rb +193 -0
  12. data/lib/doze/responder/error.rb +34 -0
  13. data/lib/doze/responder/main.rb +41 -0
  14. data/lib/doze/responder/resource.rb +262 -0
  15. data/lib/doze/responder.rb +58 -0
  16. data/lib/doze/response.rb +78 -0
  17. data/lib/doze/router/anchored_route_set.rb +68 -0
  18. data/lib/doze/router/route.rb +88 -0
  19. data/lib/doze/router/route_set.rb +34 -0
  20. data/lib/doze/router.rb +100 -0
  21. data/lib/doze/serialization/entity.rb +34 -0
  22. data/lib/doze/serialization/form_data_helpers.rb +40 -0
  23. data/lib/doze/serialization/html.rb +116 -0
  24. data/lib/doze/serialization/json.rb +29 -0
  25. data/lib/doze/serialization/multipart_form_data.rb +162 -0
  26. data/lib/doze/serialization/resource.rb +30 -0
  27. data/lib/doze/serialization/resource_proxy.rb +14 -0
  28. data/lib/doze/serialization/www_form_encoded.rb +42 -0
  29. data/lib/doze/serialization/yaml.rb +25 -0
  30. data/lib/doze/uri_template.rb +220 -0
  31. data/lib/doze/utils.rb +53 -0
  32. data/lib/doze/version.rb +3 -0
  33. data/lib/doze.rb +5 -0
  34. data/test/functional/auth_test.rb +69 -0
  35. data/test/functional/base.rb +159 -0
  36. data/test/functional/cache_header_test.rb +76 -0
  37. data/test/functional/direct_response_test.rb +16 -0
  38. data/test/functional/error_handling_test.rb +131 -0
  39. data/test/functional/get_and_conneg_test.rb +182 -0
  40. data/test/functional/media_type_extensions_test.rb +102 -0
  41. data/test/functional/media_type_test.rb +40 -0
  42. data/test/functional/method_support_test.rb +49 -0
  43. data/test/functional/non_get_method_test.rb +173 -0
  44. data/test/functional/precondition_test.rb +84 -0
  45. data/test/functional/raw_path_info_test.rb +69 -0
  46. data/test/functional/resource_representation_test.rb +14 -0
  47. data/test/functional/router_test.rb +196 -0
  48. data/test/functional/serialization_test.rb +142 -0
  49. data/test/functional/uri_template_test.rb +51 -0
  50. metadata +221 -0
@@ -0,0 +1,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
+