hyperion_http 0.1.9 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7d9226a915406ce7fd02411a0d43d091de54dbb
4
- data.tar.gz: ff9b70e966564c62dcfb54c49e39430b8480d77a
3
+ metadata.gz: 960780555d961a067c45dd406b4a77c222d3b870
4
+ data.tar.gz: a9ccdfa4d464305cd35d4d79bc392760bd393327
5
5
  SHA512:
6
- metadata.gz: bf64df0e49481f580f66f65c7100fff0fe44784f56148fc828e64a235ac754cb3d328bf3134a47cf277055efe44112b38d3062cfb0158b96f9f3a8604acec585
7
- data.tar.gz: f4db46cdf9980be0695142c9a88749e80919574265b49db3375b2398f24b29362ad8c1e2a503c33fb778e98e4e432962b6ddaf9376b6ef4208fcf0ec78e3f6e0
6
+ metadata.gz: f67e72757677c9f6c10f088d9c6965436a6442ffe93dc65e42a5654ac525f1545551693aaefd34c9f157881cbb25331caba370fe9eccae671d21332123f01b12
7
+ data.tar.gz: 1389a2fd6cc8d60fbfa0269eeb087aed6edc86790cd6ecdab6475ff9cd530aae489833ee08156571e2ecc7605d0a4abc6f33943c514e4320510e597f6fa3fcfc
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0-p247
3
+ - 2.2.2
4
4
  branches:
5
5
  only:
6
6
  - master
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.2.0](https://github.com/indigobio/hyperion/compare/v0.1.9...indigobio:v0.2.0) (2016-9-15)
2
+
3
+ - eliminate Mimic dependency to resolve conflicts in code that uses hyperion
4
+
1
5
  ## [0.1.7](https://github.com/indigobio/hyperion/compare/v0.1.6...indigobio:v0.1.7) (2015-12-14)
2
6
 
3
7
  - fake_route now works with multipart body
@@ -390,4 +394,4 @@
390
394
  it's shutting down, resulting in a timeout.
391
395
 
392
396
  ### 0.1.2
393
- - Fixed broken gemspec version for abstractivator
397
+ - Fixed broken gemspec version for abstractivator
data/hyperion.gemspec ADDED
@@ -0,0 +1 @@
1
+ hyperion_http.gemspec
@@ -1,11 +1,10 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'hyperion/aux/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = 'hyperion_http'
8
- spec.version = Hyperion::VERSION
7
+ spec.version = '0.2.1'
9
8
  spec.authors = ['Indigo BioAutomation, Inc.']
10
9
  spec.email = ['pwinton@indigobio.com']
11
10
  spec.summary = 'Ruby REST client'
@@ -30,6 +29,6 @@ Gem::Specification.new do |spec|
30
29
  spec.add_runtime_dependency 'immutable_struct', '~> 1.1'
31
30
  spec.add_runtime_dependency 'oj', '~> 2.12'
32
31
  spec.add_runtime_dependency 'typhoeus', '~> 0.7'
33
- spec.add_runtime_dependency 'mimic', '0.4.3' # pin because 0.4.4 breaks tests
32
+ spec.add_runtime_dependency 'rack'
34
33
  spec.add_runtime_dependency 'logatron'
35
34
  end
@@ -19,8 +19,7 @@ class Hyperion
19
19
  end
20
20
 
21
21
  def log_stub(rule)
22
- mr = rule.mimic_route
23
- logger.debug "Stubbed #{mr.method.to_s.upcase} #{mr.path}"
22
+ logger.debug "Stubbed #{rule.method.to_s.upcase} #{rule.path}"
24
23
  log_headers(rule.headers, logger)
25
24
  end
26
25
 
@@ -1,5 +1,4 @@
1
1
  require 'immutable_struct'
2
- require 'mimic'
3
2
  require 'hyperion/headers'
4
3
  require 'hyperion/formats'
5
4
  require 'uri'
@@ -7,27 +6,37 @@ require 'hyperion_test/fake_server'
7
6
 
8
7
  class Hyperion
9
8
  class << self
10
- # maintains a collection of fake servers, one for each base_uri.
11
- # manages rspec integration for automatic teardown after each test.
9
+ # Maintains a collection of fake servers, one for each base_uri.
10
+ # Manages rspec integration for automatic teardown after each test.
12
11
 
13
12
  include Formats
14
13
  include Headers
15
14
  include TestFrameworkHooks
16
15
  include Logger
17
16
 
17
+ # Configure routes on the server for the given base_uri
18
18
  def fake(base_uri, &routes)
19
19
  base_uri = normalized_base(base_uri)
20
- if !@running
21
- hook_teardown if can_hook_teardown? && !teardown_registered?
22
- @running = true
20
+ unless @configured
21
+ hook_reset if can_hook_reset? && !reset_registered?
22
+ @configured = true
23
23
  end
24
24
  servers[base_uri].configure(&routes)
25
25
  end
26
26
 
27
- def teardown
27
+ # Clear routes but don't stop servers. Meant to be called between tests.
28
+ # Starting/stopping servers is relatively slow. They can be reused.
29
+ def reset
30
+ servers.values.each(&:clear_routes)
31
+ @configured = false
32
+ end
33
+
34
+ # Stop all servers. This should only need to be called by tests that use
35
+ # Kim directly (like kim_spec.rb).
36
+ def teardown_cached_servers
28
37
  servers.values.each(&:teardown)
29
38
  servers.clear
30
- @running = false
39
+ @configured = false
31
40
  end
32
41
 
33
42
  private
@@ -1,54 +1,86 @@
1
1
  require 'hyperion_test/test_framework_hooks'
2
2
  require 'hyperion/aux/hash_ext'
3
- require 'hyperion_test/fake_server/dispatcher'
3
+ require 'hyperion/headers'
4
+ require 'hyperion/formats'
4
5
  require 'hyperion_test/fake_server/types'
5
6
  require 'hyperion_test/fake_server/config'
7
+ require 'hyperion_test/kim'
8
+ require 'hyperion_test/kim/matchers'
6
9
 
7
10
  class Hyperion
8
11
  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.
12
+ # Runs a Kim server configured per the specified routing rules.
13
+ include Kim::Matchers
14
+ include Headers
15
+ include Formats
13
16
 
14
- attr_accessor :port, :rules
17
+ attr_accessor :port
15
18
 
16
19
  def initialize(port)
17
20
  @port = port
18
- @rules = []
21
+ @kim = Kim.new(port: port)
22
+ @kim.start
19
23
  end
20
24
 
21
25
  def configure(&configure_routes)
22
26
  config = Config.new
23
27
  configure_routes.call(config)
24
- rules.concat(config.rules)
25
- restart_server
28
+ config.rules.each do |rule|
29
+ matcher = Kim::Matcher.and(verb(rule.verb),
30
+ res(rule.path),
31
+ req_headers(rule.headers))
32
+ handler = wrap(rule.handler, rule.rest_route)
33
+ @kim.add_handler(matcher, &handler)
34
+ end
35
+ end
36
+
37
+ def clear_routes
38
+ @kim.clear_handlers
26
39
  end
27
40
 
28
41
  def teardown
29
- rules.clear
30
- @mimic_running = true
31
- Mimic.cleanup!
42
+ @kim.stop
43
+ end
44
+
45
+ private
46
+
47
+ # Make it easier to write handlers by massaging input and output
48
+ def wrap(handler, rest_route)
49
+ proc do |req|
50
+ massage_request!(req)
51
+ resp = handler.call(req)
52
+ massage_response(resp, rest_route)
53
+ end
32
54
  end
33
55
 
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
56
+ def massage_request!(req)
57
+ if req.body && !req.body.empty?
58
+ req.body = read(req.body, :json)
40
59
  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
60
+ end
61
+
62
+ def massage_response(resp, rest_route)
63
+ if rack_response?(resp)
64
+ code, headers, body = resp
65
+ unless body.is_a?(String)
66
+ body = write(body, :json)
49
67
  end
68
+ [code, headers, body]
69
+ else
70
+ if rest_route
71
+ rd = rest_route.response_descriptor
72
+ content_type = content_type_for(rd)
73
+ format = rd
74
+ else
75
+ content_type = 'application/json'
76
+ format = :json
77
+ end
78
+ ['200', {'Content-Type' => content_type}, write(resp, format)]
50
79
  end
51
- @mimic_running = true
80
+ end
81
+
82
+ def rack_response?(resp)
83
+ resp.is_a?(Array) && resp.size == 3
52
84
  end
53
85
  end
54
86
  end
@@ -23,12 +23,11 @@ class Hyperion
23
23
  def allowed_rule(args, handler)
24
24
  if args.size == 1 && args.first.is_a?(RestRoute)
25
25
  route = args.first
26
- Rule.new(MimicRoute.new(route.method, route.uri.path), route_headers(route), handler, route)
26
+ Rule.new(route.method, route.uri.path, route_headers(route), handler, route)
27
27
  else
28
- # TODO: deprecate this
29
28
  method, path, headers = args
30
29
  headers ||= {}
31
- Rule.new(MimicRoute.new(method, path), headers, handler, nil)
30
+ Rule.new(method, path, headers, handler, nil)
32
31
  end
33
32
  end
34
33
  end
@@ -1,7 +1,9 @@
1
1
  class Hyperion
2
2
  class FakeServer
3
- MimicRoute = ImmutableStruct.new(:method, :path)
4
- Rule = ImmutableStruct.new(:mimic_route, :headers, :handler, :rest_route)
3
+ Rule = ImmutableStruct.new(:method, :path, :headers, :handler, :rest_route)
4
+ class Rule
5
+ alias_method :verb, :method
6
+ end
5
7
  Request = ImmutableStruct.new(:body)
6
8
  end
7
9
  end
@@ -0,0 +1,223 @@
1
+ require 'rack'
2
+ require 'securerandom'
3
+ require 'thread'
4
+ require 'ostruct'
5
+ require 'active_support/core_ext/string/inflections'
6
+ require 'hyperion_test/kim/matcher'
7
+
8
+ class Hyperion
9
+ class Kim
10
+ # A dumb fake web server.
11
+ # This is minimal object wrapper around Rack/WEBrick. WEBrick was chosen
12
+ # because it comes with ruby and we're not doing rocket science here.
13
+ # Kim runs Rack/WEBrick in a separate thread and keeps an array of
14
+ # handlers. A handler is simply a predicate on a request object
15
+ # and a function to handle the request should the predicate return truthy.
16
+ # When rack notifies us of a request, we dispatch it to the first handler
17
+ # with a truthy predicate.
18
+ #
19
+ # Again, what we're trying to do is very simple. Most of the existing complexity
20
+ # is due to
21
+ # - thread synchronization
22
+ # - unmangling WEBrick's header renaming
23
+ # - loosening the requirements on what a handler function must return
24
+ #
25
+ # To support path parameters (e.g., /people/:name), a predicate may return
26
+ # a Request object as a truthy value, augmented with additional params.
27
+ # When the predicate returns a Request, the augmented request object is
28
+ # passed to the handler function in place of the original request.
29
+
30
+ Handler = Struct.new(:pred, :func)
31
+ Request = Struct.new(:verb, # 'GET' | 'POST' | ...
32
+ :path, # String
33
+ :params, # OpenStruct
34
+ :headers, # Hash[String => String]
35
+ :body) # String
36
+ class Request
37
+ alias_method :method, :verb
38
+ def merge(other)
39
+ merge_params(other.params)
40
+ end
41
+ def merge_params(other_params)
42
+ params = OpenStruct.new(self.params.to_h.merge(other_params.to_h))
43
+ Request.new(verb, path, params, headers, body)
44
+ end
45
+ end
46
+
47
+ def initialize(port:)
48
+ @port = port
49
+ @handlers = []
50
+ @lock = Mutex.new # controls access to this instance of Kim (via public methods and callbacks)
51
+ end
52
+
53
+ def self.webrick_mutex
54
+ @webrick_mutex ||= Mutex.new # controls access to the Rack::Handler::WEBrick singleton
55
+ end
56
+
57
+ def start
58
+ # Notes on synchronization:
59
+ #
60
+ # The only way to start a handler is with static method ::run
61
+ # which touches singleton instance variables. webrick_mutex
62
+ # ensures only one thread is in the singleton at a time.
63
+ #
64
+ # A threadsafe queue is used to notify the calling thread
65
+ # that the server thread has started. The caller needs to
66
+ # wait so it can obtain the webrick instance.
67
+
68
+ @lock.synchronize do
69
+ raise 'Cannot restart' if @stopped
70
+ Kim.webrick_mutex.synchronize do
71
+ q = Queue.new
72
+ @thread = Thread.start do
73
+ begin
74
+ opts = {Port: @port, Logger: ::Logger.new('/dev/null'), AccessLog: []} # hide output
75
+ Rack::Handler::WEBrick.run(method(:handle_request), opts) do |webrick|
76
+ q.push(webrick)
77
+ end
78
+ ensure
79
+ $stderr.puts "Hyperion fake server on port #{@port} exited unexpectedly!" unless @stopped
80
+ end
81
+ end
82
+ @webrick = q.pop
83
+ end
84
+ end
85
+ end
86
+
87
+ def stop
88
+ @lock.synchronize do
89
+ return if @stopped
90
+ @stopped = true
91
+ @webrick.shutdown
92
+ @thread.join
93
+ @webrick = nil
94
+ @thread = nil
95
+ end
96
+ end
97
+
98
+ # Add a handler. Returns a proc that removes the handler.
99
+ def add_handler(matcher_or_pred, &handler_proc)
100
+ @lock.synchronize do
101
+ handler = Handler.new(Matcher.wrap(matcher_or_pred), handler_proc)
102
+ @handlers.unshift(handler)
103
+ remover = proc { @lock.synchronize { @handlers.delete(handler) } }
104
+ remover
105
+ end
106
+ end
107
+
108
+ def clear_handlers
109
+ @lock.synchronize do
110
+ @handlers = []
111
+ end
112
+ end
113
+
114
+ private
115
+ def handle_request(env)
116
+ @lock.synchronize do
117
+ req = request_for(env)
118
+ x = handle(req)
119
+ x = massage_response(x)
120
+ x = validate_response(x)
121
+ x
122
+ end
123
+ end
124
+
125
+ def request_for(env)
126
+ verb = env['REQUEST_METHOD']
127
+ path = env['PATH_INFO']
128
+ params = OpenStruct.new(read_query_params(env['QUERY_STRING']))
129
+ headers = read_headers(env)
130
+ body = env['rack.input'].gets
131
+ Request.new(verb, path, params, headers, body)
132
+ end
133
+
134
+ def read_query_params(query_string)
135
+ query_string
136
+ .split('&')
137
+ .map { |kv| kv.split('=') }
138
+ .to_h
139
+ end
140
+
141
+ def read_headers(env)
142
+ # similar to https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/lib/webrick/cgi.rb#L217-L226
143
+ env.each_pair
144
+ .select { |k, _| mangled_header?(k) }
145
+ .map { |k, v| [unmangle_header_key(k), v] }
146
+ .to_h
147
+ end
148
+
149
+ def mangled_header?(h)
150
+ h.start_with?('HTTP_') || %w(CONTENT_TYPE CONTENT_LENGTH).include?(h)
151
+ end
152
+
153
+ def unmangle_header_key(k)
154
+ k.gsub(/^HTTP_/, '')
155
+ .split('_')
156
+ .map(&:titlecase)
157
+ .join('-')
158
+ end
159
+
160
+ def handle(req)
161
+ pred_value, func = @handlers.lazy
162
+ .map { |h| [h.pred.call(req), h.func] }
163
+ .select { |(pv, _)| pv }
164
+ .first || [nil, no_route_matched_func]
165
+ func.call(pred_value.is_a?(Request) ? pred_value : req)
166
+ end
167
+
168
+ def massage_response(r)
169
+ if triplet?(r)
170
+ r[0] = r[0].to_s # code
171
+ r[1] = r[1] || {} # headers
172
+ r[2] = *r[2] # body/bodies (coerce to array)
173
+ r[2].map!(&:to_s)
174
+ r
175
+ elsif r.is_a?(String)
176
+ ['200', {}, [r]]
177
+ else
178
+ r
179
+ end
180
+ end
181
+
182
+ def validate_response(r)
183
+ triplet?(r) or return server_error("Invalid response, not a size-3 array: #{r.inspect}.")
184
+ http_code?(r[0]) or return server_error("Invalid response, invalid http code: #{r[0].inspect}")
185
+ headers?(r[1]) or return server_error("Invalid response, invalid header hash: #{r[1].inspect}")
186
+ bodies?(r[2]) or return server_error("Invalid response, invalid bodies array: #{r[2].inspect}")
187
+ r
188
+ end
189
+
190
+ def no_route_matched_func
191
+ proc do
192
+ ['404', error_headers, ['Request matched no routes.']]
193
+ end
194
+ end
195
+
196
+ def triplet?(x)
197
+ x.is_a?(Array) && x.size == 3
198
+ end
199
+
200
+ def http_code?(x)
201
+ return false unless x.respond_to?(:to_i)
202
+ v = x.to_i
203
+ 100 <= v && v < 600
204
+ end
205
+
206
+ def headers?(x)
207
+ # TODO: check for valid keys and values
208
+ x.is_a?(Hash)
209
+ end
210
+
211
+ def bodies?(x)
212
+ x.is_a?(Array) && x.all? { |v| v.is_a?(String) }
213
+ end
214
+
215
+ def server_error(msg)
216
+ ['500', error_headers, [msg]]
217
+ end
218
+
219
+ def error_headers
220
+ {'Content-Type' => 'text/plain'}
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,86 @@
1
+ class Hyperion
2
+ class Kim
3
+ class Matcher
4
+ # Fancy predicates for HTTP requests.
5
+ # Features:
6
+ # - and/or/not combinators
7
+ # - If a predicate raises an error, it is caught and treated as falsey. simplifies predicates.
8
+ # For example: headers['Allow'].starts_with?('application/')
9
+ # will raise if no Allow header was sent, however we really just want to treat that as
10
+ # a non-match.
11
+ # - Parameter extraction. A matcher can return an augmented Request as the truthy value.
12
+
13
+ attr_reader :func
14
+
15
+ def initialize(func=nil, &block)
16
+ @func = Matcher.wrap(block || func)
17
+ end
18
+
19
+ def call(req)
20
+ @func.call(req)
21
+ end
22
+
23
+ def and(other)
24
+ Matcher.new do |req|
25
+ (req2 = @func.call(req)) && other.call(req2)
26
+ end
27
+ end
28
+
29
+ def or(other)
30
+ Matcher.new do |req|
31
+ @func.call(req) || other.call(req)
32
+ end
33
+ end
34
+
35
+ def not
36
+ Matcher.new do |req|
37
+ @func.call(req) ? nil : req
38
+ end
39
+ end
40
+
41
+ def self.and(*ms)
42
+ m, *rest = ms
43
+ if rest.empty?
44
+ m
45
+ else
46
+ m.and(Matcher.and(*rest))
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # Coerce the return value of the function to nil/hash in case
53
+ # it returns a simple true/false.
54
+ # Update (mutate) the request params with any addition values
55
+ # gleaned by a successful match.
56
+ def self.wrap(f)
57
+ if f.is_a?(Matcher)
58
+ f
59
+ else
60
+ proc do |req|
61
+ v = coerce(f, req)
62
+ # Update the request parameters. respond_to?(:merge) is a
63
+ # compromise between outright depending on Kim::Request
64
+ # and threading a totally generic 'update' function
65
+ # through all the matcher code.
66
+ if v && req.respond_to?(:merge)
67
+ req.merge(v)
68
+ else
69
+ v
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.coerce(f, req)
76
+ case v = f.call(req)
77
+ when TrueClass then req
78
+ when FalseClass then nil
79
+ else v
80
+ end
81
+ rescue
82
+ nil # treat predicate errors as falsey
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'hyperion_test/kim/matcher'
3
+
4
+ class Hyperion
5
+ class Kim
6
+ module Matchers
7
+ # Some useful matchers to include in your code
8
+
9
+ def res(resource_pattern)
10
+ regex = resource_pattern.gsub(/:([^\/]+)/, "(?<\\1>[^\\/]+)")
11
+ Matcher.new do |req|
12
+ m = req.path.match(regex)
13
+ m && req.merge_params(m.names.zip(m.captures).to_h)
14
+ end
15
+ end
16
+
17
+ def verb(verb_to_match)
18
+ Matcher.new do |req|
19
+ req.verb.to_s.upcase == verb_to_match.to_s.upcase
20
+ end
21
+ end
22
+
23
+ def req_headers(required_headers)
24
+ Matcher.new do |req|
25
+ required_headers.each_pair.all? do |(k, v)|
26
+ hash_includes?(req.headers.to_h, k, v)
27
+ end
28
+ end
29
+ end
30
+
31
+ def req_params(required_params)
32
+ Matcher.new do |req|
33
+ required_params.each_pair.all? do |(k, v)|
34
+ hash_includes?(req.params.to_h, k, v)
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def hash_includes?(h, k, v)
42
+ (h.keys.include?(k.to_s) || h.keys.include?(k.to_sym)) && (v.nil? || (h[k.to_s] || h[k.to_sym]) == v)
43
+ end
44
+
45
+ extend self
46
+ end
47
+ end
48
+ end
@@ -2,19 +2,19 @@ require 'hyperion'
2
2
  require 'rspec/core'
3
3
 
4
4
  module TestFrameworkHooks
5
- def teardown_registered?
5
+ def reset_registered?
6
6
  rspec_after_example_hooks.any? do |hook_proc|
7
- hook_proc.source_location == method(:teardown).to_proc.source_location
7
+ hook_proc.source_location == method(:reset).to_proc.source_location
8
8
  end
9
9
  end
10
10
 
11
- def can_hook_teardown?
12
- RSpec.current_example
11
+ def can_hook_reset?
12
+ !!RSpec.current_example
13
13
  end
14
14
 
15
- def hook_teardown
15
+ def hook_reset
16
16
  hyperion = self
17
- rspec_hooks.register(:prepend, :after, :each) { hyperion.teardown }
17
+ rspec_hooks.register(:prepend, :after, :each) { hyperion.reset }
18
18
  end
19
19
 
20
20
  def rspec_after_example_hooks
@@ -110,12 +110,11 @@ describe Hyperion::Requestor do
110
110
  context 'on a 400-level response' do
111
111
 
112
112
  def example(opts)
113
- arrange(:get, [(400..499).to_a.sample, {}, opts[:response]])
113
+ code = ((400..499).to_a - [404]).sample
114
+ arrange(:get, [code, {}, opts[:response]])
114
115
  expect{request(@route)}.to raise_error HyperionError, opts[:error]
115
116
  end
116
117
 
117
-
118
-
119
118
  context 'when the response is a properly-formed error message' do
120
119
  it 'raises an error with the response message' do
121
120
  example response: '{"message":"oops"}',
@@ -73,8 +73,8 @@ describe Hyperion do
73
73
  end
74
74
 
75
75
  it 'logs requests for unstubbed routes' do
76
- route1 = RestRoute.new(:get, 'http://site.org/stuff', user_response_params)
77
- route2 = RestRoute.new(:get, 'http://site.org/things', user_response_params)
76
+ route1 = RestRoute.new(:get, 'http://somesite.org/stuff', user_response_params)
77
+ route2 = RestRoute.new(:get, 'http://somesite.org/things', user_response_params)
78
78
  Hyperion.fake(route1.uri.base) do |svr|
79
79
  svr.allow(route1) {{}}
80
80
  end
@@ -136,23 +136,23 @@ describe Hyperion do
136
136
  end
137
137
 
138
138
  it 'allows multiple fake servers to be created' do
139
- Hyperion.fake('http://google.com') do |svr|
140
- svr.allow(:get, '/welcome') { success_response({'text' => 'hello from google'}) }
139
+ Hyperion.fake('http://somesite.org') do |svr|
140
+ svr.allow(:get, '/welcome') { success_response({'text' => 'hello from somesite'}) }
141
141
  end
142
142
 
143
- Hyperion.fake('http://indigo.com:3000') do |svr|
144
- svr.allow(:get, '/welcome') { success_response({'text' => 'hello from indigo@3000'}) }
143
+ Hyperion.fake('http://indigo.com:80') do |svr|
144
+ svr.allow(:get, '/welcome') { success_response({'text' => 'hello from indigo@80'}) }
145
145
  end
146
146
 
147
147
  Hyperion.fake('http://indigo.com:4000') do |svr|
148
148
  svr.allow(:get, '/welcome') { success_response({'text' => 'hello from indigo@4000'}) }
149
149
  end
150
150
 
151
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/welcome', user_response_params))
152
- expect(result.body).to eql({'text' => 'hello from google'})
151
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/welcome', user_response_params))
152
+ expect(result.body).to eql({'text' => 'hello from somesite'})
153
153
 
154
- result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:3000/welcome', user_response_params))
155
- expect(result.body).to eql({'text' => 'hello from indigo@3000'})
154
+ result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:80/welcome', user_response_params))
155
+ expect(result.body).to eql({'text' => 'hello from indigo@80'})
156
156
 
157
157
  result = Hyperion.request(RestRoute.new(:get, 'http://indigo.com:4000/welcome', user_response_params))
158
158
  expect(result.body).to eql({'text' => 'hello from indigo@4000'})
@@ -176,37 +176,37 @@ describe Hyperion do
176
176
  end
177
177
 
178
178
  it 'allows routes to be augmented' do
179
- Hyperion.fake('http://google.com') do |svr|
179
+ Hyperion.fake('http://somesite.org') do |svr|
180
180
  svr.allow(:get, '/old') { success_response({'text' => 'old'}) }
181
181
  svr.allow(:get, '/hello') { success_response({'text' => 'hello'}) }
182
182
  svr.allow(RestRoute.new(:get, '/users/0', user_response_params)) { success_response({'user' => 'old user'}) }
183
183
  end
184
184
 
185
185
  # smoke test that the server is up and running
186
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/hello', user_response_params))
186
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/hello', user_response_params))
187
187
  expect(result.body).to eql({'text' => 'hello'})
188
188
 
189
189
  # augment the routes
190
- Hyperion.fake('http://google.com') do |svr|
190
+ Hyperion.fake('http://somesite.org') do |svr|
191
191
  svr.allow(:get, '/hello') { success_response({'text' => 'aloha'}) }
192
192
  svr.allow(:get, '/goodbye') { success_response({'text' => 'goodbye'}) }
193
193
  svr.allow(RestRoute.new(:get, '/users/0', user_response_params)) { success_response({'user' => 'new user'}) }
194
194
  end
195
195
 
196
196
  # untouched routes are left alone
197
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/old', user_response_params))
197
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/old', user_response_params))
198
198
  expect(result.body).to eql({'text' => 'old'})
199
199
 
200
200
  # restating the route replaces it (last one wins)
201
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/hello', user_response_params))
201
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/hello', user_response_params))
202
202
  expect(result.body).to eql({'text' => 'aloha'})
203
203
 
204
204
  # new routes can be added
205
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/goodbye', user_response_params))
205
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/goodbye', user_response_params))
206
206
  expect(result.body).to eql({'text' => 'goodbye'})
207
207
 
208
208
  # restating a route routes that uses headers to differentiate replaces it (last one wins)
209
- result = Hyperion.request(RestRoute.new(:get, 'http://google.com/users/0', user_response_params))
209
+ result = Hyperion.request(RestRoute.new(:get, 'http://somesite.org/users/0', user_response_params))
210
210
  expect(result.body).to eql({'user' => 'new user'})
211
211
  end
212
212
 
@@ -0,0 +1,126 @@
1
+ require 'rspec'
2
+ require 'hyperion_test/kim'
3
+ require 'hyperion_test/kim/matcher'
4
+ require 'hyperion_test/kim/matchers'
5
+
6
+ describe Hyperion::Kim::Matcher do
7
+ Matcher = Hyperion::Kim::Matcher
8
+ Request = Hyperion::Kim::Request
9
+ include Hyperion::Kim::Matchers
10
+
11
+ describe '::new' do
12
+ it 'creates a matcher' do
13
+ m = Matcher.new(proc { |x| x >= 0 })
14
+ expect(m.call(1)).to be_truthy
15
+ expect(m.call(-1)).to be_falsey
16
+ end
17
+ it 'accepts a block' do
18
+ m = Matcher.new { |x| x >= 0 }
19
+ expect(m.call(1)).to be_truthy
20
+ expect(m.call(-1)).to be_falsey
21
+ end
22
+ it 'can return a request' do
23
+ m = res('/greet/:name')
24
+ r = m.call(req('/greet/kim'))
25
+ expect(r.params.name).to eql 'kim'
26
+ expect(m.call(req('/text/kim'))).to be_falsey
27
+ end
28
+ it 'treats errors as falsey' do
29
+ m = Matcher.new { raise 'oops' }
30
+ expect(m.call(:anything)).to be_falsey
31
+ end
32
+ end
33
+ describe '#call' do
34
+ it 'invokes the predicate' do
35
+ m = Matcher.new { |x| x >= 0 }
36
+ expect(m.call(1)).to be_truthy
37
+ expect(m.call(-1)).to be_falsey
38
+ end
39
+ end
40
+ describe '#and' do
41
+ it 'combines predicates with boolean AND' do
42
+ positive = Matcher.new { |x| x > 0 }
43
+ even = Matcher.new(&:even?)
44
+ m = positive.and(even)
45
+ expect(m.call(2)).to be_truthy
46
+ expect(m.call(-2)).to be_falsey
47
+ expect(m.call(1)).to be_falsey
48
+ expect(m.call(-1)).to be_falsey
49
+ end
50
+ it 'merges result hash' do
51
+ matcher = res('/greet/:name').and(res('/:action/kim'))
52
+ verify_path_match matcher, '/greet/kim', yields_params: {name: 'kim', action: 'greet'}
53
+ verify_path_does_not_match matcher, '/greet/bob'
54
+ verify_path_does_not_match matcher, '/text/kim'
55
+ verify_path_does_not_match matcher, '/text/bob'
56
+ end
57
+ end
58
+ describe '#or' do
59
+ it 'combines predicates with boolean OR' do
60
+ positive = Matcher.new { |x| x > 0 }
61
+ even = Matcher.new(&:even?)
62
+ m = positive.or(even)
63
+ expect(m.call(2)).to be_truthy
64
+ expect(m.call(-2)).to be_truthy
65
+ expect(m.call(1)).to be_truthy
66
+ expect(m.call(-1)).to be_falsey
67
+ end
68
+ it 'returns result hash of first matching predicate' do
69
+ matcher = res('/greet/:name').or(res('/:action/kim'))
70
+ verify_path_match matcher, '/greet/kim', yields_params: {name: 'kim'}
71
+ verify_path_match matcher, '/text/kim', yields_params: {action: 'text'}
72
+ verify_path_does_not_match matcher, '/text/bob'
73
+ end
74
+ end
75
+ describe '#not' do
76
+ it 'returns a negated predicate' do
77
+ even = Matcher.new(&:even?)
78
+ odd = even.not
79
+ expect(odd.call(1)).to be_truthy
80
+ expect(odd.call(2)).to be_falsey
81
+ end
82
+ end
83
+ describe '::and' do
84
+ it 'combines predicates with boolean AND' do
85
+ positive = Matcher.new { |x| x > 0 }
86
+ even = Matcher.new(&:even?)
87
+ mult10 = Matcher.new { |x| x % 10 == 0 }
88
+ m = Matcher.and(positive, even, mult10)
89
+ expect(m.call(10)).to be_truthy
90
+ expect(m.call(-10)).to be_falsey
91
+ end
92
+ end
93
+ context 'when the request is augmented with params' do
94
+ let(:matcher) do
95
+ match_people = res('/people/:name')
96
+ name_starts_with_k = Matcher.new { |r| r.params.name.start_with?('k') }
97
+ match_people.and(name_starts_with_k)
98
+ end
99
+ it 'the params are updated as the predicate executes' do
100
+ verify_path_match matcher, '/people/kim'
101
+ verify_path_does_not_match matcher, '/people/kim'
102
+ verify_path_does_not_match matcher, '/people/bob'
103
+ verify_path_does_not_match matcher, '/idiots/kanye'
104
+ end
105
+ it 'the original request is unchanged' do
106
+ original_request = req('/people/kim')
107
+ augmented_request = matcher.call(original_request)
108
+ expect(augmented_request.params.name).to eql 'kim'
109
+ expect(original_request.params.name).to be nil
110
+ end
111
+ end
112
+
113
+ def req(path)
114
+ Request.new('GET', path, OpenStruct.new, {}, nil)
115
+ end
116
+
117
+ def verify_path_match(matcher, path, yields_params: nil)
118
+ result = matcher.call(req(path))
119
+ expect(result).to be_truthy
120
+ expect(result.params.to_h).to eql(yields_params) if yields_params
121
+ end
122
+
123
+ def verify_path_does_not_match(matcher, path)
124
+ expect(matcher.call(res(path))).to be_falsey
125
+ end
126
+ end
@@ -0,0 +1,58 @@
1
+ require 'rspec'
2
+ require 'hyperion_test/kim'
3
+ require 'hyperion_test/kim/matchers'
4
+
5
+ describe Hyperion::Kim::Matchers do
6
+ include Hyperion::Kim::Matchers
7
+ describe '#res' do
8
+ it 'matches resource paths' do
9
+ m = res('/people/:name/birthplace/:city')
10
+ r = m.call(req(path: '/people/kim/birthplace/la'))
11
+ expect(r.params.name).to eql 'kim'
12
+ expect(r.params.city).to eql 'la'
13
+ expect(m.call(req(path: '/people/kim/home/la'))).to be_falsey
14
+ end
15
+ end
16
+ describe '#verb' do
17
+ it 'matches the HTTP verb' do
18
+ expect(verb('GET').call(req(verb: 'GET'))).to be_truthy
19
+ expect(verb('PUT').call(req(verb: 'put'))).to be_truthy
20
+ expect(verb(:post).call(req(verb: 'POST'))).to be_truthy
21
+ expect(verb('GET').call(req(verb: 'PUT'))).to be_falsey
22
+ end
23
+ end
24
+ describe '#req_headers' do
25
+ it 'matches headers' do
26
+ m = req_headers('Allow' => 'application/json')
27
+ expect(m.call(req(headers: {'Allow' => 'application/json'}))).to be_truthy
28
+ expect(m.call(req(headers: {'Allow' => 'text/html'}))).to be_falsey
29
+ expect(m.call(req(headers: {}))).to be_falsey
30
+ end
31
+ it 'only checks for presence if value is nil' do
32
+ m = req_headers('Allow' => nil)
33
+ expect(m.call(req(headers: {'Allow' => 'application/json'}))).to be_truthy
34
+ expect(m.call(req(headers: {'Allow' => 'text/html'}))).to be_truthy
35
+ expect(m.call(req(headers: {}))).to be_falsey
36
+ end
37
+ end
38
+ describe '#req_params' do
39
+ it 'matches params' do
40
+ m = req_params(a: 1, 'b' => 2)
41
+ expect(m.call(req(params: params(a: 1, b: 2)))).to be_truthy
42
+ end
43
+ it 'only checks for presence if value is nil' do
44
+ m = req_params(c: nil)
45
+ expect(m.call(req(params: params(c: 1)))).to be_truthy
46
+ expect(m.call(req(params: params(c: 2)))).to be_truthy
47
+ expect(m.call(req(params: params(z: 2)))).to be_falsey
48
+ end
49
+ end
50
+
51
+ def req(attrs)
52
+ Hyperion::Kim::Request.new(*attrs.values_at(*Hyperion::Kim::Request.members))
53
+ end
54
+
55
+ def params(*args)
56
+ OpenStruct.new(*args)
57
+ end
58
+ end
@@ -0,0 +1,157 @@
1
+ require 'rspec'
2
+ require 'hyperion_test/kim'
3
+ require 'typhoeus'
4
+ require 'hyperion_test'
5
+
6
+ describe Hyperion::Kim do
7
+ before(:all) do
8
+ Hyperion.teardown_cached_servers
9
+ @port = 9001
10
+ @kim = Hyperion::Kim.new(port: @port)
11
+ @kim.start
12
+ end
13
+ before(:each) { @kim.clear_handlers }
14
+ after(:all) { @kim.stop }
15
+ attr_accessor :kim
16
+
17
+ let!(:always) { proc { true } }
18
+
19
+ context 'normal operation' do
20
+ it 'routes to a block' do
21
+ kim.add_handler(always) { 'Hello, World!' }
22
+ expect(get_body('/')).to eql 'Hello, World!'
23
+ end
24
+ it 'routes to the most recently added handler with a true predicate' do
25
+ kim.add_handler(proc{false}) { 'a' }
26
+ kim.add_handler(proc{true}) { 'b' }
27
+ kim.add_handler(proc{true}) { 'c' }
28
+ kim.add_handler(proc{false}) { 'd' }
29
+ expect(get_body('/')).to eql 'c'
30
+ end
31
+ it 'allows multiple servers running in parallel' do
32
+ kim2 = Hyperion::Kim.new(port: 9002)
33
+ begin
34
+ kim2.start
35
+ kim.add_handler(always) { '1' }
36
+ kim2.add_handler(always) { '2' }
37
+ expect(Typhoeus.get('http://localhost:9001').body).to eql '1'
38
+ expect(Typhoeus.get('http://localhost:9002').body).to eql '2'
39
+ kim2.stop
40
+ expect(Typhoeus.get('http://localhost:9001').body).to eql '1'
41
+ expect(Typhoeus.get('http://localhost:9002').success?).to be false
42
+ ensure
43
+ kim2.stop
44
+ end
45
+ end
46
+ it 'continues working after a handler error' do
47
+ crash_command = proc { |r| r.path.include?('crash') }
48
+ greet_command = proc { |r| r.path.include?('greet') }
49
+ kim.add_handler(crash_command) { raise 'oops' }
50
+ kim.add_handler(greet_command) { 'hello' }
51
+ expect(get_code('/crash')).to eql 500
52
+ expect(get_code('/greet')).to eql 200
53
+ end
54
+ end
55
+
56
+ describe '#add_handler' do
57
+ it 'returns a remover proc' do
58
+ remover = kim.add_handler(always) { 'foo' }
59
+ expect(get_body('/')).to eql 'foo'
60
+ remover.call
61
+ expect(get_code('/')).to eql 404
62
+ end
63
+ end
64
+
65
+ describe '#clear_handlers' do
66
+ it 'clears all handlers' do
67
+ kim.add_handler(always) { 'foo' }
68
+ expect(get_body('/')).to eql 'foo'
69
+ kim.clear_handlers
70
+ expect(get_code('/')).to eql 404
71
+ end
72
+ end
73
+
74
+ describe 'a handler' do
75
+ it 'receives the HTTP verb' do
76
+ verb = nil
77
+ method = nil
78
+ kim.add_handler(always) do |r|
79
+ verb = r.verb
80
+ method = r.method
81
+ end
82
+ post('/')
83
+ expect(verb).to eql 'POST'
84
+ expect(method).to eql 'POST'
85
+ end
86
+ it 'receives the resource path' do
87
+ path = nil
88
+ kim.add_handler(always) { |r| path = r.path; '' }
89
+ get('/foo/bar')
90
+ expect(path).to eql '/foo/bar'
91
+ end
92
+ it 'receives the params' do
93
+ params = nil
94
+ kim.add_handler(proc{ |req| req.merge_params(d: '4', e: '5') }) { |r| params = r.params; '' }
95
+ get('/foo/bar?a=1&b=2&c=3')
96
+ expect(params.a).to eql '1'
97
+ expect(params[:b]).to eql '2'
98
+ expect(params['c']).to eql '3'
99
+ expect(params.d).to eql '4'
100
+ expect(params.e).to eql '5'
101
+ end
102
+ it 'receives the request headers' do
103
+ headers = nil
104
+ kim.add_handler(always) { |r| headers = r.headers; '' }
105
+ get('/', headers: {'Accept' => 'application/json', 'Content-Type' => 'text/html'})
106
+ expect(headers['Accept']).to eql 'application/json'
107
+ expect(headers['Content-Type']).to eql 'text/html'
108
+ end
109
+ it 'receives the request body' do
110
+ body = nil
111
+ kim.add_handler(always) { |r| body = r.body; '' }
112
+ post('/', body: 'please do something')
113
+ expect(body).to eql 'please do something'
114
+ end
115
+ it 'can return a string' do
116
+ kim.add_handler(always) { 'hello' }
117
+ r = get('/')
118
+ expect(r.code).to eql 200
119
+ expect(r.body).to eql 'hello'
120
+ end
121
+ it 'can return a rack response' do
122
+ kim.add_handler(always) { ['400', {'Content-Type' => 'application/greeting'}, ['oops']] }
123
+ r = get('/')
124
+ expect(r.body).to eql 'oops'
125
+ expect(r.code).to eql 400
126
+ expect(r.headers['Content-Type']).to eql 'application/greeting'
127
+ end
128
+ it 'rack response requirements are somewhat loosened' do
129
+ kim.add_handler(always) { [400, nil, 'oops'] }
130
+ r = get('/')
131
+ expect(r.body).to eql 'oops'
132
+ expect(r.code).to eql 400
133
+ end
134
+ end
135
+
136
+ def get_body(path)
137
+ response = get(path)
138
+ expect(response.success?).to be true
139
+ response.body
140
+ end
141
+
142
+ def base_uri
143
+ "http://localhost:#{@port}"
144
+ end
145
+
146
+ def get_code(path)
147
+ get(path).code
148
+ end
149
+
150
+ def get(path, headers: {})
151
+ Typhoeus.get(File.join(base_uri, path), headers: headers)
152
+ end
153
+
154
+ def post(path, headers: {}, body: nil)
155
+ Typhoeus.post(File.join(base_uri, path), headers: headers, body: body)
156
+ end
157
+ end
data/update_version.sh ADDED
@@ -0,0 +1,52 @@
1
+ #!/bin/bash -e
2
+
3
+ #This script is used during the release process. It is not intended to be ran manually.
4
+
5
+ VERSION="$1"
6
+ VERSION="${VERSION:?"must provide version as first parameter"}"
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")"; pwd)"
8
+
9
+ updateVersion(){
10
+ updateGemspec
11
+ commitStagedFiles "Update version to ${VERSION}"
12
+ }
13
+
14
+ updateGemspec(){
15
+ echo -e "\nUpdating gemspec version"
16
+ local gemspecPath="${SCRIPT_DIR}/hyperion_http.gemspec"
17
+ sed -i 's/\(\.version\s*=\s*\).*/\1'"'${VERSION}'/" "${gemspecPath}"
18
+ stageFiles "${gemspecPath}"
19
+ }
20
+
21
+ stageAndCommit(){
22
+ local msg="$1"
23
+ shift
24
+ local files=( "$@" )
25
+ stageFiles "${files[@]}"
26
+ commitStagedFiles "${msg}"
27
+ }
28
+
29
+ stageFiles(){
30
+ local files=( "$@" )
31
+ git add "${files[@]}"
32
+ }
33
+
34
+ commitStagedFiles(){
35
+ local msg="$1"
36
+ if thereAreStagedFiles; then
37
+ git commit -m "${msg}"
38
+ else
39
+ echo "No changes to commit"
40
+ fi
41
+ }
42
+
43
+ thereAreStagedFiles(){
44
+ git update-index -q --ignore-submodules --refresh
45
+ if git diff-index --cached --quiet HEAD --ignore-submodules -- ; then
46
+ return 1;
47
+ else
48
+ return 0;
49
+ fi
50
+ }
51
+
52
+ updateVersion
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperion_http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Indigo BioAutomation, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-17 00:00:00.000000000 Z
11
+ date: 2016-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -165,19 +165,19 @@ dependencies:
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0.7'
167
167
  - !ruby/object:Gem::Dependency
168
- name: mimic
168
+ name: rack
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - '='
171
+ - - ">="
172
172
  - !ruby/object:Gem::Version
173
- version: 0.4.3
173
+ version: '0'
174
174
  type: :runtime
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - '='
178
+ - - ">="
179
179
  - !ruby/object:Gem::Version
180
- version: 0.4.3
180
+ version: '0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: logatron
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -208,6 +208,7 @@ files:
208
208
  - README.md
209
209
  - Rakefile
210
210
  - build.yml
211
+ - hyperion.gemspec
211
212
  - hyperion_http.gemspec
212
213
  - lib/hyperion.rb
213
214
  - lib/hyperion/aux/bug_error.rb
@@ -215,7 +216,6 @@ files:
215
216
  - lib/hyperion/aux/logger.rb
216
217
  - lib/hyperion/aux/typho.rb
217
218
  - lib/hyperion/aux/util.rb
218
- - lib/hyperion/aux/version.rb
219
219
  - lib/hyperion/formats.rb
220
220
  - lib/hyperion/headers.rb
221
221
  - lib/hyperion/hyperion.rb
@@ -238,8 +238,10 @@ files:
238
238
  - lib/hyperion_test/fake.rb
239
239
  - lib/hyperion_test/fake_server.rb
240
240
  - lib/hyperion_test/fake_server/config.rb
241
- - lib/hyperion_test/fake_server/dispatcher.rb
242
241
  - lib/hyperion_test/fake_server/types.rb
242
+ - lib/hyperion_test/kim.rb
243
+ - lib/hyperion_test/kim/matcher.rb
244
+ - lib/hyperion_test/kim/matchers.rb
243
245
  - lib/hyperion_test/spec_helper.rb
244
246
  - lib/hyperion_test/test_framework_hooks.rb
245
247
  - spec/fixtures/test
@@ -254,9 +256,13 @@ files:
254
256
  - spec/lib/hyperion/types/hyperion_uri_spec.rb
255
257
  - spec/lib/hyperion_spec.rb
256
258
  - spec/lib/hyperion_test/fake_route_spec.rb
259
+ - spec/lib/hyperion_test/kim/matcher_spec.rb
260
+ - spec/lib/hyperion_test/kim/matchers_spec.rb
261
+ - spec/lib/hyperion_test/kim_spec.rb
257
262
  - spec/lib/types_spec.rb
258
263
  - spec/spec_helper.rb
259
264
  - spec/support/core_helpers.rb
265
+ - update_version.sh
260
266
  homepage: ''
261
267
  licenses:
262
268
  - MIT
@@ -277,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
283
  version: '0'
278
284
  requirements: []
279
285
  rubyforge_project:
280
- rubygems_version: 2.4.5
286
+ rubygems_version: 2.6.2
281
287
  signing_key:
282
288
  specification_version: 4
283
289
  summary: Ruby REST client
@@ -294,7 +300,9 @@ test_files:
294
300
  - spec/lib/hyperion/types/hyperion_uri_spec.rb
295
301
  - spec/lib/hyperion_spec.rb
296
302
  - spec/lib/hyperion_test/fake_route_spec.rb
303
+ - spec/lib/hyperion_test/kim/matcher_spec.rb
304
+ - spec/lib/hyperion_test/kim/matchers_spec.rb
305
+ - spec/lib/hyperion_test/kim_spec.rb
297
306
  - spec/lib/types_spec.rb
298
307
  - spec/spec_helper.rb
299
308
  - spec/support/core_helpers.rb
300
- has_rdoc:
@@ -1,3 +0,0 @@
1
- class Hyperion
2
- VERSION = '0.1.9'
3
- end
@@ -1,74 +0,0 @@
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