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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +4 -0
- data/.travis.yml +7 -0
- data/CHANGES.md +145 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +421 -0
- data/Rakefile +11 -0
- data/hyperion_http.gemspec +34 -0
- data/lib/hyperion/aux/bug_error.rb +2 -0
- data/lib/hyperion/aux/hash_ext.rb +5 -0
- data/lib/hyperion/aux/logger.rb +54 -0
- data/lib/hyperion/aux/typho.rb +9 -0
- data/lib/hyperion/aux/util.rb +18 -0
- data/lib/hyperion/aux/version.rb +3 -0
- data/lib/hyperion/formats.rb +69 -0
- data/lib/hyperion/headers.rb +43 -0
- data/lib/hyperion/hyperion.rb +79 -0
- data/lib/hyperion/requestor.rb +88 -0
- data/lib/hyperion/result_handling/dispatch_dsl.rb +67 -0
- data/lib/hyperion/result_handling/dispatching_hyperion_result.rb +10 -0
- data/lib/hyperion/result_handling/result_maker.rb +64 -0
- data/lib/hyperion/types/client_error_code.rb +9 -0
- data/lib/hyperion/types/client_error_detail.rb +46 -0
- data/lib/hyperion/types/client_error_response.rb +50 -0
- data/lib/hyperion/types/hyperion_error.rb +6 -0
- data/lib/hyperion/types/hyperion_result.rb +24 -0
- data/lib/hyperion/types/hyperion_status.rb +10 -0
- data/lib/hyperion/types/hyperion_uri.rb +97 -0
- data/lib/hyperion/types/payload_descriptor.rb +9 -0
- data/lib/hyperion/types/response_descriptor.rb +21 -0
- data/lib/hyperion/types/rest_route.rb +20 -0
- data/lib/hyperion.rb +15 -0
- data/lib/hyperion_test/fake.rb +64 -0
- data/lib/hyperion_test/fake_server/config.rb +36 -0
- data/lib/hyperion_test/fake_server/dispatcher.rb +74 -0
- data/lib/hyperion_test/fake_server/types.rb +7 -0
- data/lib/hyperion_test/fake_server.rb +54 -0
- data/lib/hyperion_test/spec_helper.rb +19 -0
- data/lib/hyperion_test/test_framework_hooks.rb +34 -0
- data/lib/hyperion_test.rb +2 -0
- data/spec/lib/hyperion/aux/util_spec.rb +29 -0
- data/spec/lib/hyperion/formats_spec.rb +84 -0
- data/spec/lib/hyperion/headers_spec.rb +61 -0
- data/spec/lib/hyperion/logger_spec.rb +60 -0
- data/spec/lib/hyperion/test_spec.rb +222 -0
- data/spec/lib/hyperion/types/client_error_response_spec.rb +52 -0
- data/spec/lib/hyperion/types/hyperion_result_spec.rb +17 -0
- data/spec/lib/hyperion/types/hyperion_uri_spec.rb +113 -0
- data/spec/lib/hyperion_spec.rb +187 -0
- data/spec/lib/superion_spec.rb +151 -0
- data/spec/lib/types_spec.rb +46 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/core_helpers.rb +5 -0
- 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,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,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,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
|