hyperion_http 0.1.2

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 (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