request_interceptor 0.2.1 → 0.3.0

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: 244056547ed0ff2bc1acaeb6fa12f6aec7eb7fb7
4
- data.tar.gz: 8b0719757447a76119044afefb543020ed1718a8
3
+ metadata.gz: 15e0184221194d0cd9222006c1739e23f7c81a89
4
+ data.tar.gz: 5bb90d1e9b0b9b4d0eee2cadb6015e324db86e42
5
5
  SHA512:
6
- metadata.gz: 98452c1e2592dbdb2746a1eea5bf4627bd135743b0074864a1aab8b553bef146b0190950ac1d57311f492fe685697e757e24cffb5ecb8ba18c6569914c318731
7
- data.tar.gz: 398a7146eb773b04749296906832216b93587aa05a279374df7c692b0def45a60fdbcf05b26bb0e7d88f8f32e151ad52023ae6886ef0621d766b2628412c1f87
6
+ metadata.gz: e36776a7b15b8994100a480d5ebda2d12373d09b54d9cd48c22c2488b2623d3143b19f078f1bd067a8a90956b1d3be853388f5875616ffcbc8d8061e4c0576a2
7
+ data.tar.gz: 74d7a949efa97b601324b38ef7666741294ee5a00e6bad56d328d2e53f923a79cd3d3bdb0801bf66ec1242b0129b58ad415f0a589cccc227ccd9a3afa4e67233
@@ -1,18 +1,22 @@
1
1
  require "sinatra/base"
2
2
 
3
3
  class RequestInterceptor::Application < Sinatra::Base
4
- def self.customize(&customizations)
5
- RequestInterceptor.define(self, &customizations)
6
- end
4
+ class << self
5
+ def customize(&customizations)
6
+ RequestInterceptor.define(self, &customizations)
7
+ end
7
8
 
8
- def self.intercept(host, *args, &test)
9
- RequestInterceptor.run(host => self.new(*args), &test)
10
- end
9
+ def intercept(pattern, *args, &test)
10
+ RequestInterceptor.run(pattern => self.new(*args), &test)
11
+ end
11
12
 
12
- def self.host(host)
13
- define_singleton_method(:intercept) do |*args, &test|
14
- super(host, *args, &test)
13
+ def match(pattern)
14
+ define_singleton_method(:intercept) do |*args, &test|
15
+ super(pattern, *args, &test)
16
+ end
15
17
  end
18
+
19
+ alias host match
16
20
  end
17
21
 
18
22
  configure do
@@ -1,3 +1,3 @@
1
1
  class RequestInterceptor
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,60 @@
1
+ class RequestInterceptor::WebMockManager
2
+ WebMockConfigurationCache = Struct.new(:request_stubs, :callbacks, :allow_net_connect, :allow_localhost, :show_body_diff, :show_stubbing_instructions, :enabled_previously)
3
+
4
+ def initialize(applications, callback = nil)
5
+ @applications = applications
6
+ @callback = callback
7
+ end
8
+
9
+ def run_simulation
10
+ original_webmock_configuration = setup
11
+
12
+ yield
13
+ ensure
14
+ teardown(original_webmock_configuration)
15
+ end
16
+
17
+ protected
18
+
19
+ attr_reader :callback
20
+ attr_reader :applications
21
+
22
+ private
23
+
24
+ def setup
25
+ original_configuration = WebMockConfigurationCache.new
26
+ original_configuration.enabled_previously = WebMock.enabled?
27
+ original_configuration.request_stubs = WebMock::StubRegistry.instance.request_stubs.dup || []
28
+ original_configuration.callbacks = WebMock::CallbackRegistry.callbacks.dup || []
29
+ original_configuration.allow_net_connect = WebMock::Config.instance.allow_net_connect
30
+ original_configuration.allow_localhost = WebMock::Config.instance.allow_localhost
31
+ original_configuration.show_body_diff = WebMock::Config.instance.show_body_diff
32
+ original_configuration.show_stubbing_instructions = WebMock::Config.instance.show_stubbing_instructions
33
+
34
+ WebMock.after_request(&callback) unless callback.nil?
35
+
36
+ applications.each do |application|
37
+ WebMock.stub_request(:any, application.pattern).to_rack(application)
38
+ end
39
+
40
+ WebMock.allow_net_connect!
41
+ WebMock.hide_body_diff!
42
+ WebMock.hide_stubbing_instructions!
43
+ WebMock.enable!
44
+
45
+ original_configuration
46
+ end
47
+
48
+ def teardown(original_configuration)
49
+ WebMock::Config.instance.allow_net_connect = original_configuration.allow_net_connect
50
+ WebMock::Config.instance.allow_localhost = original_configuration.allow_localhost
51
+ WebMock::Config.instance.show_body_diff = original_configuration.show_body_diff
52
+ WebMock::Config.instance.show_stubbing_instructions = original_configuration.show_stubbing_instructions
53
+ WebMock::CallbackRegistry.reset
54
+ original_configuration.callbacks.each do |callback_settings|
55
+ WebMock.after_request(callback_settings[:options], &callback_settings[:block])
56
+ end
57
+ WebMock::StubRegistry.instance.request_stubs = original_configuration.request_stubs
58
+ WebMock.disable! unless original_configuration.enabled_previously
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ module RequestInterceptor::WebMockPatches
2
+ def enable!
3
+ @enabled = true
4
+ super
5
+ end
6
+
7
+ def disable!
8
+ @enabled = false
9
+ super
10
+ end
11
+
12
+ def enabled?
13
+ !!@enabled
14
+ end
15
+
16
+ def disabled?
17
+ !enabled
18
+ end
19
+ end
@@ -1,22 +1,44 @@
1
1
  require "request_interceptor/version"
2
2
 
3
- require "net/http"
4
- require "rack/mock"
3
+ require "webmock"
4
+ require "uri"
5
5
 
6
6
  class RequestInterceptor
7
7
  Transaction = Struct.new(:request, :response)
8
8
 
9
- Restart = Struct.new(:required, :instructions) do
10
- protected :required
11
- def required?
12
- !!required
9
+ module RequestBackwardsCompatibility
10
+ def path
11
+ uri.request_uri
12
+ end
13
+
14
+ def uri
15
+ URI.parse(super.to_s)
16
+ end
17
+
18
+ def method
19
+ super.to_s.upcase
13
20
  end
14
21
  end
15
22
 
16
- GET = "GET".freeze
17
- POST = "POST".freeze
18
- PUT = "PUT".freeze
19
- DELETE = "DELETE".freeze
23
+ class ApplicationWrapper < SimpleDelegator
24
+ attr_reader :pattern
25
+
26
+ def initialize(pattern, application)
27
+ @pattern =
28
+ case pattern
29
+ when String
30
+ %r{://#{Regexp.escape(pattern)}/}
31
+ else
32
+ pattern
33
+ end
34
+
35
+ super(application)
36
+ end
37
+
38
+ def intercepts?(uri)
39
+ !!pattern.match(uri.normalize.to_s)
40
+ end
41
+ end
20
42
 
21
43
  def self.template=(template)
22
44
  @template =
@@ -36,173 +58,35 @@ class RequestInterceptor
36
58
  Class.new(super_class || template, &application_definition)
37
59
  end
38
60
 
39
- def self.run(*args, &simulation)
40
- new(*args).run(&simulation)
61
+ def self.run(applications, &simulation)
62
+ new(applications).run(&simulation)
41
63
  end
42
64
 
43
65
  attr_reader :applications
44
66
  attr_reader :transactions
45
67
 
46
68
  def initialize(applications)
47
- @applications = {}
69
+ @applications = applications.map { |pattern, application| ApplicationWrapper.new(pattern, application) }
48
70
  @transactions = []
49
-
50
- applications.each { |pattern, application| self[pattern] = application }
51
- end
52
-
53
- def [](pattern)
54
- @applications[pattern]
55
- end
56
-
57
- def []=(pattern, application)
58
- @applications[pattern] = application
59
71
  end
60
72
 
61
73
  def run(&simulation)
62
- clear_transaction_log
63
- cache_original_net_http_methods
64
- override_net_http_methods
65
- simulation.call
66
- transactions
67
- ensure
68
- restore_net_http_methods
69
- end
70
-
71
- def request(http_context, request, body, &block)
72
- # use Net::HTTP set_body_internal to
73
- # keep the same behaviour as Net::HTTP
74
- request.set_body_internal(body)
75
- response = nil
76
-
77
- if mock_request = mock_request_for_application(http_context, request)
78
- mock_response = dispatch_mock_request(request, mock_request)
79
-
80
- # create response
81
- status = RequestInterceptor::Status.from_code(mock_response.status)
82
- response = status.response_class.new("1.1", status.value, status.description)
83
-
84
- # copy header to response
85
- mock_response.original_headers.each do |k, v|
86
- response.add_field(k, v)
87
- end
88
-
89
- # copy uri
90
- response.uri = request.uri
91
-
92
- # copy body to response
93
- response.body = mock_response.body
94
-
95
- # replace Net::HTTP::Response#read_body
96
- def response.read_body(_, &block)
97
- block.call(@body) unless block.nil?
98
- @body
99
- end
100
-
101
- # yield the response because Net::HTTP#request does
102
- block.call(response) unless block.nil?
103
-
104
- # log intercepted transaction
105
- log_transaction(request, response)
106
- else
107
- response = real_request(http_context, request, body, &block)
108
- end
109
-
110
- response
111
- end
74
+ transactions = []
112
75
 
113
- def start(http_context, &block)
114
- self.restart = Restart.new(true, block)
115
- http_context.instance_variable_set(:@started, true)
116
- return block.call(http_context) if block
117
- http_context
118
- end
119
-
120
- def finish(http_context)
121
- http_context.instance_variable_set(:@started, false)
122
- nil
123
- end
124
-
125
- protected
126
-
127
- attr_writer :restart
128
-
129
- def restart
130
- @restart || Restart.new(false)
131
- end
132
-
133
- private
134
-
135
- def clear_transaction_log
136
- @transactions = []
137
- end
138
-
139
- def cache_original_net_http_methods
140
- @original_request_method = Net::HTTP.instance_method(:request)
141
- @original_start_method = Net::HTTP.instance_method(:start)
142
- @original_finish_method = Net::HTTP.instance_method(:finish)
143
- end
144
-
145
- def override_net_http_methods
146
- runner = self
147
-
148
- Net::HTTP.class_eval do
149
- define_method(:start) { |&block| runner.start(self, &block) }
150
- define_method(:finish) { runner.finish(self) }
151
- define_method(:request) { |request, body = nil, &block| runner.request(self, request, body, &block) }
76
+ request_logging = ->(request, response) do
77
+ next unless applications.any? { |application| application.intercepts?(request.uri) }
78
+ request.extend(RequestBackwardsCompatibility)
79
+ transactions << Transaction.new(request, response)
152
80
  end
153
- end
154
81
 
155
- def real_request(http_context, request, body, &block)
156
- http_context.finish if restart.required?
82
+ WebMockManager.new(applications, request_logging).run_simulation(&simulation)
157
83
 
158
- restore_net_http_methods(http_context)
159
-
160
- if restart.required?
161
- http_context.start(&restart.instructions)
162
- self.restart = nil
163
- end
164
-
165
- http_context.request(request, body, &block)
166
- end
167
-
168
- def restore_net_http_methods(instance = nil)
169
- if instance.nil?
170
- Net::HTTP.send(:define_method, :request, @original_request_method)
171
- Net::HTTP.send(:define_method, :start, @original_start_method)
172
- Net::HTTP.send(:define_method, :finish, @original_finish_method)
173
- else
174
- instance.define_singleton_method(:request, @original_request_method)
175
- instance.define_singleton_method(:start, @original_start_method)
176
- instance.define_singleton_method(:finish, @original_finish_method)
177
- end
178
- end
179
-
180
- def mock_request_for_application(http_context, request)
181
- _, application = applications.find { |pattern, _| pattern === http_context.address }
182
- Rack::MockRequest.new(application) if application
183
- end
184
-
185
- def dispatch_mock_request(request, mock_request)
186
- rack_env = request.to_hash
187
-
188
- case request.method
189
- when GET
190
- mock_request.get(request.path, rack_env)
191
- when POST
192
- mock_request.post(request.path, rack_env.merge(input: request.body))
193
- when PUT
194
- mock_request.put(request.path, rack_env.merge(input: request.body))
195
- when DELETE
196
- mock_request.delete(request.path, rack_env)
197
- else
198
- raise NotImplementedError, "Simulating #{request.method} is not supported"
199
- end
200
- end
201
-
202
- def log_transaction(request, response)
203
- transactions << RequestInterceptor::Transaction.new(request, response)
84
+ transactions
204
85
  end
205
86
  end
206
87
 
207
- require "request_interceptor/application"
208
- require "request_interceptor/status"
88
+ require_relative "request_interceptor/application"
89
+ require_relative "request_interceptor/webmock_manager"
90
+ require_relative "request_interceptor/webmock_patches"
91
+
92
+ WebMock.singleton_class.prepend(RequestInterceptor::WebMockPatches)
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency "sinatra"
22
22
  spec.add_runtime_dependency "rack"
23
+ spec.add_runtime_dependency "webmock", "~> 3.0"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.9"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request_interceptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Tennhard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-04-18 00:00:00.000000000 Z
12
+ date: 2017-05-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: webmock
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: bundler
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -115,8 +129,9 @@ files:
115
129
  - circle.yml
116
130
  - lib/request_interceptor.rb
117
131
  - lib/request_interceptor/application.rb
118
- - lib/request_interceptor/status.rb
119
132
  - lib/request_interceptor/version.rb
133
+ - lib/request_interceptor/webmock_manager.rb
134
+ - lib/request_interceptor/webmock_patches.rb
120
135
  - request_interceptor.gemspec
121
136
  homepage: http://github.com/t6d/request_interceptor
122
137
  licenses:
@@ -1,34 +0,0 @@
1
- require 'rack/utils'
2
-
3
- class RequestInterceptor::Status < Struct.new(:value, :description)
4
- STATUSES = Rack::Utils::HTTP_STATUS_CODES
5
-
6
- def self.from_code(code, description = nil)
7
- description = STATUSES.fetch(code.to_i, "Unknown") if description.nil?
8
- new(code.to_s, description)
9
- end
10
-
11
- def response_class
12
- self.class.response_class(value)
13
- end
14
-
15
- def self.response_class(code)
16
- @response_classes ||=
17
- begin
18
- wrapped_classes = Hash.new { Net::HTTPUnknownResponse }
19
- Net::HTTPResponse::CODE_TO_OBJ.inject(wrapped_classes) do |classes, (code, original_class)|
20
- new_class = Class.new(original_class) do
21
- attr_accessor :body
22
- end
23
-
24
- new_class.define_singleton_method(:name) { original_class.name }
25
- new_class.define_singleton_method(:to_s) { original_class.name }
26
-
27
- classes[code] = new_class
28
- classes
29
- end
30
- end
31
-
32
- @response_classes[code]
33
- end
34
- end