hyperion_http 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGES.md +145 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +421 -0
  9. data/Rakefile +11 -0
  10. data/hyperion_http.gemspec +34 -0
  11. data/lib/hyperion/aux/bug_error.rb +2 -0
  12. data/lib/hyperion/aux/hash_ext.rb +5 -0
  13. data/lib/hyperion/aux/logger.rb +54 -0
  14. data/lib/hyperion/aux/typho.rb +9 -0
  15. data/lib/hyperion/aux/util.rb +18 -0
  16. data/lib/hyperion/aux/version.rb +3 -0
  17. data/lib/hyperion/formats.rb +69 -0
  18. data/lib/hyperion/headers.rb +43 -0
  19. data/lib/hyperion/hyperion.rb +79 -0
  20. data/lib/hyperion/requestor.rb +88 -0
  21. data/lib/hyperion/result_handling/dispatch_dsl.rb +67 -0
  22. data/lib/hyperion/result_handling/dispatching_hyperion_result.rb +10 -0
  23. data/lib/hyperion/result_handling/result_maker.rb +64 -0
  24. data/lib/hyperion/types/client_error_code.rb +9 -0
  25. data/lib/hyperion/types/client_error_detail.rb +46 -0
  26. data/lib/hyperion/types/client_error_response.rb +50 -0
  27. data/lib/hyperion/types/hyperion_error.rb +6 -0
  28. data/lib/hyperion/types/hyperion_result.rb +24 -0
  29. data/lib/hyperion/types/hyperion_status.rb +10 -0
  30. data/lib/hyperion/types/hyperion_uri.rb +97 -0
  31. data/lib/hyperion/types/payload_descriptor.rb +9 -0
  32. data/lib/hyperion/types/response_descriptor.rb +21 -0
  33. data/lib/hyperion/types/rest_route.rb +20 -0
  34. data/lib/hyperion.rb +15 -0
  35. data/lib/hyperion_test/fake.rb +64 -0
  36. data/lib/hyperion_test/fake_server/config.rb +36 -0
  37. data/lib/hyperion_test/fake_server/dispatcher.rb +74 -0
  38. data/lib/hyperion_test/fake_server/types.rb +7 -0
  39. data/lib/hyperion_test/fake_server.rb +54 -0
  40. data/lib/hyperion_test/spec_helper.rb +19 -0
  41. data/lib/hyperion_test/test_framework_hooks.rb +34 -0
  42. data/lib/hyperion_test.rb +2 -0
  43. data/spec/lib/hyperion/aux/util_spec.rb +29 -0
  44. data/spec/lib/hyperion/formats_spec.rb +84 -0
  45. data/spec/lib/hyperion/headers_spec.rb +61 -0
  46. data/spec/lib/hyperion/logger_spec.rb +60 -0
  47. data/spec/lib/hyperion/test_spec.rb +222 -0
  48. data/spec/lib/hyperion/types/client_error_response_spec.rb +52 -0
  49. data/spec/lib/hyperion/types/hyperion_result_spec.rb +17 -0
  50. data/spec/lib/hyperion/types/hyperion_uri_spec.rb +113 -0
  51. data/spec/lib/hyperion_spec.rb +187 -0
  52. data/spec/lib/superion_spec.rb +151 -0
  53. data/spec/lib/types_spec.rb +46 -0
  54. data/spec/spec_helper.rb +3 -0
  55. data/spec/support/core_helpers.rb +5 -0
  56. metadata +280 -0
@@ -0,0 +1,97 @@
1
+ require 'uri'
2
+ require 'delegate'
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'rack/utils'
5
+ require 'abstractivator/array_ext'
6
+
7
+ class HyperionUri < SimpleDelegator
8
+ # An enhanced version of URI. Namely,
9
+ # - the base uri is a first class citizen,
10
+ # - it accepts a hash containing query key/values, and
11
+ # - it form-encodes query values that are arrays.
12
+
13
+ attr_accessor :query_hash
14
+
15
+ def initialize(uri, query_hash={})
16
+ @uri = make_ruby_uri(uri)
17
+ query_from_uri = parse_query(@uri.query)
18
+ additional_query_params = validate_query(query_hash)
19
+ @query_hash = query_from_uri.merge(additional_query_params)
20
+ __setobj__(@uri)
21
+ end
22
+
23
+ def query
24
+ query_string(@query_hash)
25
+ end
26
+
27
+ def query=(query)
28
+ @query_hash = parse_query(query)
29
+ end
30
+
31
+ def to_s
32
+ fixed = @uri.dup
33
+ fixed.query = query
34
+ make_ruby_uri(fixed).to_s
35
+ end
36
+
37
+ def inspect
38
+ "#<HyperionUri:0x#{(object_id << 1).to_s(16)} #{to_s}>"
39
+ end
40
+
41
+ # @return [String] the URI base e.g., "h\ttp://somehost:80"
42
+ def base
43
+ "#{scheme}://#{host}:#{port}"
44
+ end
45
+
46
+ def base=(uri)
47
+ uri = uri.is_a?(HyperionUri) ? uri : HyperionUri.new(uri)
48
+ self.scheme = uri.scheme
49
+ self.host = uri.host
50
+ self.port = uri.port
51
+ end
52
+
53
+ private
54
+
55
+ def validate_query(query)
56
+ query ||= {}
57
+ query.is_a?(Hash) or fail 'query must be a hash'
58
+ query.values.all?(&method(:simple_value?)) or fail 'query values must be simple'
59
+ query.stringify_keys
60
+ end
61
+
62
+ def simple_value?(x)
63
+ case x
64
+ when Array; x.all?(&method(:primitive_value?))
65
+ else; primitive_value?(x)
66
+ end
67
+ end
68
+
69
+ def primitive_value?(x)
70
+ x.is_a?(String) || x.is_a?(Numeric) || x.is_a?(Symbol)
71
+ end
72
+
73
+ def parse_query(query)
74
+ query ||= ''
75
+ Rack::Utils.parse_nested_query(query)
76
+ end
77
+
78
+ def query_string(query_hash)
79
+ return nil if query_hash == {}
80
+ sorted = query_hash.map{|(k, v)| [k.to_s, stringify(v)]}.sort_by(&:key).to_h
81
+ Rack::Utils.build_nested_query(sorted)
82
+ end
83
+
84
+ def stringify(x)
85
+ x.is_a?(Array) ? x.map(&:to_s) : x.to_s
86
+ end
87
+
88
+ def make_ruby_uri(x)
89
+ input = x.is_a?(HyperionUri) ? x.to_s : x
90
+
91
+ # URI is an oddball. It's a module but also a method on Kernel.
92
+ # Since this class is a SimpleDelegator and SimpleDelegator is
93
+ # a BasicObject, we need to pick the method off of Kernel.
94
+ # We don't want to include Kernel because that would mess up delegation.
95
+ Kernel.instance_method(:URI).bind(self).call(input)
96
+ end
97
+ end
@@ -0,0 +1,9 @@
1
+ class PayloadDescriptor
2
+ # describes the payload sent in POST/PUT/PATCH
3
+
4
+ attr_reader :format
5
+
6
+ def initialize(format)
7
+ @format = format
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'hyperion/headers'
2
+
3
+ class ResponseDescriptor
4
+ # Describes properties of an acceptable response
5
+
6
+ include Hyperion::Headers
7
+
8
+ attr_reader :type, :version, :format
9
+
10
+ # @param type [String]
11
+ # @param version [Integer]
12
+ # @param format [Symbol] :json
13
+ def initialize(type, version, format)
14
+ @type, @version, @format = type, version, format
15
+ end
16
+
17
+ def to_s
18
+ short_mimetype(self)
19
+ end
20
+ end
21
+
@@ -0,0 +1,20 @@
1
+ require 'hyperion/types/hyperion_uri'
2
+
3
+ class RestRoute
4
+ attr_reader :method, :uri, :response_descriptor, :payload_descriptor
5
+
6
+ # @param method [Symbol] the HTTP method
7
+ # @param uri [String, URI]
8
+ # @param response_descriptor [ResponseDescriptor]
9
+ # @param payload_descriptor [PayloadDescriptor]
10
+ def initialize(method, uri, response_descriptor=nil, payload_descriptor=nil)
11
+ @method = method
12
+ @uri = HyperionUri.new(uri)
13
+ @response_descriptor = response_descriptor
14
+ @payload_descriptor = payload_descriptor
15
+ end
16
+
17
+ def to_s
18
+ "#{method.to_s.upcase} #{uri}"
19
+ end
20
+ end
data/lib/hyperion.rb ADDED
@@ -0,0 +1,15 @@
1
+ # hyperion's dependencies
2
+ require 'immutable_struct'
3
+ require 'typhoeus'
4
+ require 'oj'
5
+ require 'continuation'
6
+ require 'abstractivator/proc_ext'
7
+ require 'abstractivator/enumerable_ext'
8
+ require 'active_support/core_ext/object/blank'
9
+ require 'active_support/core_ext/object/try'
10
+ require 'active_support/core_ext/module/attribute_accessors'
11
+
12
+ # ensure the requirer gets everything (except hyperion_test)
13
+ require 'hyperion/hyperion'
14
+ require 'hyperion/requestor'
15
+ Dir.glob(File.join(File.dirname(__FILE__), 'hyperion/types/**/*.rb')).each { |path| require_relative(path) }
@@ -0,0 +1,64 @@
1
+ require 'immutable_struct'
2
+ require 'mimic'
3
+ require 'hyperion/headers'
4
+ require 'hyperion/formats'
5
+ require 'uri'
6
+ require 'hyperion_test/fake_server'
7
+
8
+ class Hyperion
9
+ class << self
10
+ # maintains a collection of fake servers, one for each base_uri.
11
+ # manages rspec integration for automatic teardown after each test.
12
+
13
+ include Formats
14
+ include Headers
15
+ include TestFrameworkHooks
16
+ include Logger
17
+
18
+ def fake(base_uri, &routes)
19
+ base_uri = normalized_base(base_uri)
20
+ if !@running
21
+ hook_teardown if can_hook_teardown? && !teardown_registered?
22
+ @running = true
23
+ end
24
+ servers[base_uri].configure(&routes)
25
+ end
26
+
27
+ def teardown
28
+ servers.values.each(&:teardown)
29
+ servers.clear
30
+ @running = false
31
+ end
32
+
33
+ private
34
+
35
+ def servers
36
+ @servers ||= Hash.new{|hash, key| hash[key] = FakeServer.new(next_port)}
37
+ end
38
+
39
+ def next_port
40
+ @last_port ||= 9000
41
+ @last_port += 1
42
+ end
43
+
44
+ private
45
+
46
+ def normalized_base(uri)
47
+ HyperionUri.new(uri).base
48
+ end
49
+
50
+ # hook into the production code so we can redirect requests to the appropriate fake server
51
+ def transform_uri(uri)
52
+ server_uri = servers.keys.detect{|server_uri| normalized_base(server_uri) == uri.base}
53
+ if server_uri
54
+ new_uri = HyperionUri.new(uri)
55
+ new_uri.base = "http://localhost:#{servers[server_uri].port}"
56
+ logger.debug "Hyperion is redirecting #{uri} ==> #{new_uri}"
57
+ new_uri
58
+ else
59
+ uri
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ class Hyperion
2
+ class FakeServer
3
+ class Config
4
+ # this is passed to the block to allow the caller to configure the fake server
5
+
6
+ include Hyperion::Headers
7
+ include Hyperion::Logger
8
+
9
+ def rules
10
+ @rules ||= []
11
+ end
12
+
13
+ # allow(route)
14
+ # allow(method, path, headers={})
15
+ def allow(*args, &handler)
16
+ rule = allowed_rule(args, handler)
17
+ rules << rule
18
+ log_stub(rule)
19
+ end
20
+
21
+ private
22
+
23
+ def allowed_rule(args, handler)
24
+ if args.size == 1 && args.first.is_a?(RestRoute)
25
+ route = args.first
26
+ Rule.new(MimicRoute.new(route.method, route.uri.path), route_headers(route), handler, route)
27
+ else
28
+ # TODO: deprecate this
29
+ method, path, headers = args
30
+ headers ||= {}
31
+ Rule.new(MimicRoute.new(method, path), headers, handler, nil)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ require 'hyperion/headers'
2
+
3
+ class Hyperion
4
+ class FakeServer
5
+ class Dispatcher
6
+ # Directs an incoming request to the correct handler
7
+
8
+ include Hyperion::Formats
9
+ include Hyperion::Headers
10
+
11
+ def initialize(rules)
12
+ @rules = rules
13
+ end
14
+
15
+ def dispatch(mimic_route, request)
16
+ rule = find_matching_rule(mimic_route, request)
17
+ rule or return [404, {}, "Not stubbed: #{mimic_route.inspect} #{request.env}"]
18
+ request = make_req_obj(request.body.read, request.env['CONTENT_TYPE'])
19
+ response = rule.handler.call(request)
20
+ if rack_response?(response)
21
+ code, headers, body = *response
22
+ [code, headers, write(body, :json)]
23
+ else
24
+ if rule.rest_route
25
+ rd = rule.rest_route.response_descriptor
26
+ [200, {'Content-Type' => content_type_for(rd)}, write(response, rd)]
27
+ else
28
+ # better to return a 500 than raise an error, since we're executing in the forked server.
29
+ [500, {}, "An 'allow' block must return a rack-style response if it was not passed a RestRoute"]
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :rules
37
+
38
+ def rack_response?(x)
39
+ x.is_a?(Array) && x.size == 3 && x.first.is_a?(Integer) && x.drop(1).any?{|y| !y.is_a?(Integer)}
40
+ end
41
+
42
+ def find_matching_rule(mimic_route, request)
43
+ matching_rules = rules.select{|rule| rule.mimic_route == mimic_route}
44
+ matching_rules.reverse.detect{|rule| headers_match?(rule.headers, request.env)}
45
+ # reverse so that if there are duplicates, the last one wins
46
+ end
47
+
48
+ def make_req_obj(raw_body, content_type)
49
+ body = raw_body.empty? ? '' : read(raw_body, format_for(content_type))
50
+ Request.new(body)
51
+ end
52
+
53
+ def headers_match?(rule_headers, actual_headers)
54
+ sinatrize_headers(rule_headers).subhash?(actual_headers)
55
+ end
56
+
57
+ def sinatrize_headers(headers)
58
+ headers.map{|k, v| [sinatra_header(k), v]}.to_h
59
+ end
60
+
61
+ def sinatra_header(header)
62
+ # TODO: there should be a function in Sinatra that does this already
63
+ cased_header = header.upcase.gsub('-', '_')
64
+ case cased_header
65
+ when 'ACCEPT' then 'HTTP_ACCEPT'
66
+ when 'EXPECT' then 'HTTP_EXPECT'
67
+ when 'HOST' then 'HTTP_HOST'
68
+ when 'USER_AGENT' then 'HTTP_USER_AGENT'
69
+ else cased_header
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,7 @@
1
+ class Hyperion
2
+ class FakeServer
3
+ MimicRoute = ImmutableStruct.new(:method, :path)
4
+ Rule = ImmutableStruct.new(:mimic_route, :headers, :handler, :rest_route)
5
+ Request = ImmutableStruct.new(:body)
6
+ end
7
+ end
@@ -0,0 +1,54 @@
1
+ require 'hyperion_test/test_framework_hooks'
2
+ require 'hyperion/aux/hash_ext'
3
+ require 'hyperion_test/fake_server/dispatcher'
4
+ require 'hyperion_test/fake_server/types'
5
+ require 'hyperion_test/fake_server/config'
6
+
7
+ class Hyperion
8
+ class FakeServer
9
+ # Runs a Mimic server configured per the specified routing rules.
10
+ # Restarts the server when the rules change.
11
+ # The server must be restarted because it runs in a forked process
12
+ # and it is easier to kill it than try to communicate with it.
13
+
14
+ attr_accessor :port, :rules
15
+
16
+ def initialize(port)
17
+ @port = port
18
+ @rules = []
19
+ end
20
+
21
+ def configure(&configure_routes)
22
+ config = Config.new
23
+ configure_routes.call(config)
24
+ rules.concat(config.rules)
25
+ restart_server
26
+ end
27
+
28
+ def teardown
29
+ rules.clear
30
+ @mimic_running = true
31
+ Mimic.cleanup!
32
+ end
33
+
34
+ def restart_server
35
+ server = self
36
+ dispatcher = Dispatcher.new(rules)
37
+ if @mimic_running
38
+ Mimic.cleanup!
39
+ Mimic::Server.instance.instance_variable_get(:@thread).join
40
+ end
41
+ Mimic.mimic(port: @port) do
42
+ # this block executes in a strange context, which is why we
43
+ # have to close over server and dispatcher
44
+ server.rules.map(&:mimic_route).uniq.each do |mimic_route|
45
+ # register the route handlers. this is basically Sinatra.
46
+ send(mimic_route.method, mimic_route.path) do
47
+ dispatcher.dispatch(mimic_route, request)
48
+ end
49
+ end
50
+ end
51
+ @mimic_running = true
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ require 'hyperion_test'
2
+
3
+ # A simple wrapper around Hyperion::fake for the typical
4
+ # use case of one faked route. The return value can either be specified
5
+ # as an argument or as a function of the request (using the block).
6
+ #
7
+ # @param [RestRoute] route The route to handle
8
+ # @param [Hash, String] return_value The data to return in response to a request
9
+ # @yield [Hyperion::FakeServer::Request] Yields a request object containing the deserialized request body
10
+ # @yieldreturn [Hash, String, rack_response] The data to return in response to a request
11
+ def fake_route(route, return_value=nil, &block)
12
+ if return_value && block
13
+ fail 'cannot provide both a return_value and block'
14
+ end
15
+ block = block || proc{return_value}
16
+ Hyperion.fake(route.uri.base) do |svr|
17
+ svr.allow(route, &block)
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ require 'hyperion'
2
+ require 'rspec/core'
3
+
4
+ module TestFrameworkHooks
5
+ def teardown_registered?
6
+ rspec_after_example_hooks.any? do |hook_proc|
7
+ hook_proc.source_location == method(:teardown).to_proc.source_location
8
+ end
9
+ end
10
+
11
+ def can_hook_teardown?
12
+ RSpec.current_example
13
+ end
14
+
15
+ def hook_teardown
16
+ hyperion = self
17
+ rspec_hooks.register(:prepend, :after, :each) { hyperion.teardown }
18
+ end
19
+
20
+ def rspec_after_example_hooks
21
+ if rspec_hooks.respond_to?(:[]) # approximately rspec 3.1.0
22
+ rspec_hooks[:after][:example].to_a.map(&:block)
23
+ else # approximately rspec 3.3.0
24
+ default_if_no_hooks = nil
25
+ hook_collection = rspec_hooks.send(:hooks_for, :after, :example) {default_if_no_hooks}
26
+ return [] unless hook_collection
27
+ hook_collection.items_and_filters.map(&:first).map(&:block)
28
+ end
29
+ end
30
+
31
+ def rspec_hooks
32
+ RSpec.current_example.example_group.hooks
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require 'hyperion'
2
+ Dir.glob(File.join(File.dirname(__FILE__), 'hyperion_test/**/*.rb')).each{|path| require_relative(path)}
@@ -0,0 +1,29 @@
1
+ require 'rspec'
2
+ require 'hyperion/aux/util'
3
+
4
+ class Hyperion
5
+ describe Util do
6
+
7
+ describe '::nil_if_error' do
8
+ it 'catches StandardErrors in the block' do
9
+ expect(Util.nil_if_error { raise 'oops' }).to be_nil
10
+ end
11
+ it 'does not catch Exceptions in the block' do
12
+ expect{Util.nil_if_error { raise Exception }}.to raise_error Exception
13
+ end
14
+ end
15
+
16
+ describe '::guard' do
17
+ it 'raises a BugException if the value is invalid' do
18
+ expect{Util.guard_param(7, 'a string', String)}.to report 'You passed me 7, which is not a string'
19
+ expect{Util.guard_param('foo', 'any old number', Numeric)}.to report 'You passed me "foo", which is not any old number'
20
+ expect{Util.guard_param(:zero, 'callable') { |x| x.respond_to?(:call) }}.to report 'You passed me :zero, which is not callable'
21
+ end
22
+
23
+ def report(msg)
24
+ raise_error BugError, msg
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'time'
3
+ require 'hyperion'
4
+
5
+ class Hyperion
6
+ describe Formats do
7
+ include Formats
8
+
9
+ describe '#write' do
10
+ it 'returns the input if the input is a string or nil' do
11
+ expect(write('hello', :json)).to eql 'hello'
12
+ expect(write(nil, :json)).to be_nil
13
+ end
14
+ it 'returns the input if format is nil' do
15
+ expect(write('hello', nil)).to eql 'hello'
16
+ end
17
+ it 'accepts format as a symbol' do
18
+ expect(write({'a' => 1}, :json)).to eql '{"a":1}'
19
+ end
20
+ it 'accepts format as a descriptor' do
21
+ descriptor = double(format: :json)
22
+ expect(write({'a' => 1}, descriptor)).to eql '{"a":1}'
23
+ end
24
+
25
+ context 'formats' do
26
+ context 'when writing json' do
27
+ it 'writes hashes with string keys' do
28
+ expect(write({'a' => 1}, :json)).to be_json_eql '{"a":1}'
29
+ end
30
+ it 'writes hashes with symbol keys' do
31
+ expect(write({a: 1}, :json)).to be_json_eql '{"a":1}'
32
+ end
33
+ context 'when writing times' do
34
+ let!(:time) { Time.parse('2015-02-13 08:40:20.321 +1200').localtime('+12:00') }
35
+ it 'writes Time objects in UTC ISO 8601 format using the timezone that was passed in with milliseconds precision' do
36
+ expect(write({'a' => time}, :json)).to be_json_eql '{"a":"' + time.localtime('+12:00').iso8601(3) + '"}'
37
+ end
38
+ it 'preserves default behavior for non-hyperion code' do
39
+ expect(Oj.dump({'a' => time})).to start_with '{"a":{"^t":' # be non-specific, since this changes based on something outside the control of bundler
40
+ end
41
+ end
42
+ end
43
+ it 'allows protobuf format but just passes it through' do
44
+ expect(write('x', :protobuf)).to eql 'x'
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#read' do
50
+ it 'returns nil if input is nil' do
51
+ expect(read(nil, :json)).to be_nil
52
+ end
53
+ it 'returns the input if format is nil' do
54
+ expect(read('abc', nil)).to eql 'abc'
55
+ end
56
+ it 'returns the input if the input is not parseable' do
57
+ expect(read('abc', :json)).to eql 'abc'
58
+ end
59
+ context 'when reading json' do
60
+ it 'accepts format as a symbol' do
61
+ expect(read('{"a":1}', :json)).to eql({'a' => 1})
62
+ end
63
+ it 'reads times as strings' do
64
+ # sadly, Oj does not appear to support reading real Time objects, even when using
65
+ # its own representation ({"^t":1423834820.321})
66
+ expect(read('{"a":"2015-02-13T13:40:20.321Z"}', :json)['a']).to eql '2015-02-13T13:40:20.321Z'
67
+ end
68
+ end
69
+ it 'accepts format as a descriptor' do
70
+ descriptor = double(format: :json)
71
+ expect(read('{"a":1}', descriptor)).to eql({'a' => 1})
72
+ end
73
+
74
+ context 'formats' do
75
+ it 'read json' do
76
+ expect(read('{"a":1}', :json)).to eql({'a' => 1})
77
+ end
78
+ it 'allows protobuf format but just passes it through' do
79
+ expect(read('x', :protobuf)).to eql 'x'
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,61 @@
1
+ require 'hyperion'
2
+ require 'hyperion/headers'
3
+
4
+ class Hyperion
5
+ describe Headers do
6
+ include Headers
7
+
8
+ before :each do
9
+ Hyperion.configure do |config|
10
+ config.vendor_string = 'schmindigo-descent'
11
+ end
12
+ end
13
+
14
+ let!(:uri){'http://somesite.org'}
15
+
16
+ describe '#route_headers' do
17
+ it 'creates an accept header for the response descriptor' do
18
+ headers = route_headers(RestRoute.new(:get, uri, ResponseDescriptor.new('ttt', 999, :json)))
19
+ expect(headers['Accept']).to eql 'application/vnd.schmindigo-descent.ttt-v999+json'
20
+ end
21
+ it 'creates a content-type header for the payload descriptor' do
22
+ headers = route_headers(RestRoute.new(:get, uri, ResponseDescriptor.new('ttt', 999, :json), PayloadDescriptor.new(:json)))
23
+ expect(headers['Content-Type']).to eql 'application/json'
24
+ end
25
+ end
26
+
27
+ describe '#content_type_for' do
28
+ it 'accepts format as a symbol' do
29
+ expect(content_type_for(:json)).to eql 'application/json'
30
+ end
31
+ it 'accepts format as a descriptor' do
32
+ descriptor = double(format: :json)
33
+ expect(content_type_for(descriptor)).to eql 'application/json'
34
+ end
35
+ it 'returns the content type for the given format' do
36
+ expect(content_type_for(:json)).to eql 'application/json'
37
+ expect(content_type_for(:protobuf)).to eql 'application/x-protobuf'
38
+ end
39
+ it 'returns application/octet-stream if the format is unknown' do
40
+ expect(content_type_for(:aaa)).to eql 'application/octet-stream'
41
+ end
42
+ end
43
+
44
+ describe '#format_for' do
45
+ it 'returns the format for the given content type' do
46
+ expect(format_for('application/json')).to eql :json
47
+ expect(format_for('application/x-protobuf')).to eql :protobuf
48
+ end
49
+ it 'raises an error if the content type is unknown' do
50
+ expect{format_for('aaa/bbb')}.to raise_error
51
+ end
52
+ end
53
+
54
+ describe '#short_mimetype' do
55
+ it 'returns the short mime type' do
56
+ expect(short_mimetype(ResponseDescriptor.new('ttt', 999, :json))).to eql 'ttt-v999+json'
57
+ end
58
+ end
59
+
60
+ end
61
+ end