hyperion_http 0.1.9 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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