stub_requests 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +5 -9
  3. data/.rubocop.yml +1 -0
  4. data/CHANGELOG.md +23 -0
  5. data/README.md +9 -9
  6. data/Rakefile +7 -5
  7. data/lib/stub_requests.rb +30 -16
  8. data/lib/stub_requests/api.rb +45 -26
  9. data/lib/stub_requests/callback.rb +3 -1
  10. data/lib/stub_requests/callback_registry.rb +9 -55
  11. data/lib/stub_requests/concerns/argument_validation.rb +47 -0
  12. data/lib/stub_requests/concerns/property.rb +114 -0
  13. data/lib/stub_requests/concerns/property/validator.rb +137 -0
  14. data/lib/stub_requests/concerns/register_verb.rb +110 -0
  15. data/lib/stub_requests/configuration.rb +19 -2
  16. data/lib/stub_requests/dsl.rb +5 -6
  17. data/lib/stub_requests/dsl/method_definition.rb +2 -8
  18. data/lib/stub_requests/endpoint.rb +22 -23
  19. data/lib/stub_requests/endpoint_registry.rb +157 -0
  20. data/lib/stub_requests/exceptions.rb +28 -10
  21. data/lib/stub_requests/request_stub.rb +29 -14
  22. data/lib/stub_requests/service.rb +55 -7
  23. data/lib/stub_requests/service_registry.rb +30 -79
  24. data/lib/stub_requests/stub_registry.rb +22 -80
  25. data/lib/stub_requests/stub_requests.rb +8 -5
  26. data/lib/stub_requests/uri.rb +0 -17
  27. data/lib/stub_requests/utils/fuzzy.rb +70 -0
  28. data/lib/stub_requests/version.rb +1 -1
  29. data/lib/stub_requests/webmock/builder.rb +9 -51
  30. data/lib/stub_requests/webmock/stub_registry_extension.rb +1 -1
  31. data/lib/tasks/changelog.rake +1 -7
  32. data/stub_requests.gemspec +1 -0
  33. data/update_docs.sh +2 -2
  34. metadata +27 -8
  35. data/lib/stub_requests/argument_validation.rb +0 -48
  36. data/lib/stub_requests/endpoint_stub.rb +0 -89
  37. data/lib/stub_requests/endpoints.rb +0 -246
  38. data/lib/stub_requests/hash_util.rb +0 -32
  39. data/lib/stub_requests/observable.rb +0 -18
  40. data/lib/stub_requests/property.rb +0 -112
  41. data/lib/stub_requests/property/validator.rb +0 -135
@@ -13,79 +13,42 @@ module StubRequests
13
13
  # @author Mikael Henriksson <mikael@zoolutions.se>
14
14
  #
15
15
  class ServiceRegistry
16
+ # extend "Forwardable"
17
+ # @!parse extend Forwardable
18
+ extend Forwardable
19
+
20
+ # includes "Singleton"
21
+ # @!parse include Singleton
16
22
  include Singleton
23
+ # includes "Enumerable"
24
+ # @!parse include Enumerable
17
25
  include Enumerable
18
26
 
19
- # Register a service in the service registry
20
- #
27
+ delegate [:each, :[], :[]=, :keys, :delete] => :services
28
+
21
29
  #
22
- # @param [Symbol] service_id a descriptive id for the service
23
- # @param [Symbol] service_uri the uri used to call the service
30
+ # @!attribute [rw] services
31
+ # @return [Concurrent::Map<Symbol, Service>] a map with services
32
+ attr_reader :services
33
+
24
34
  #
25
- # @example Register a service with endpoints
26
- # register_service(:documents, "https://company.com/api/v1") do
27
- # get "documents/:id", as: :show
28
- # get "documents", as: :index
29
- # post "documents", as: :create
30
- # patch "documents/:id", as: :update
31
- # delete "documents/:id", as: :destroy
32
- # end
35
+ # Initialize a new instance (used by Singleton)
33
36
  #
34
- # @return [Service] a new service or a previously registered service
35
37
  #
36
- def self.register_service(service_id, service_uri, &block)
37
- service = instance.register(service_id, service_uri)
38
- Docile.dsl_eval(service.endpoints, &block) if block.present?
39
- service
38
+ def initialize
39
+ @services = Concurrent::Map.new
40
40
  end
41
41
 
42
42
  #
43
- # Stub a request to a registered service endpoint
43
+ # Returns the size of the registry
44
44
  #
45
45
  #
46
- # @param [Symbol] service_id the id of a registered service
47
- # @param [Symbol] endpoint_id the id of a registered endpoint
48
- # @param [Hash<Symbol>] route_params a map with route parameters
46
+ # @return [Integer]
49
47
  #
50
- # @note the kind of timeout error raised by webmock is depending on the HTTP client used
51
- #
52
- # @example Stub a request to a registered service endpoint
53
- # stub_endpoint(:google_api, :get_map_location)
54
- # .to_return(body: "No content", status: 204)
55
- #
56
- # @example Stub a request to a registered service endpoint using block
57
- # stub_endpoint(:documents, :index) do
58
- # with(headers: { "Accept" => "application/json" }}})
59
- # to_return(body: "No content", status: 204)
60
- # end
61
- #
62
- # @return [WebMock::RequestStub] a mocked request
63
- #
64
- def self.stub_endpoint(service_id, endpoint_id, route_params = {}, &callback)
65
- service, endpoint, uri = StubRequests::URI.for_service_endpoint(service_id, endpoint_id, route_params)
66
- webmock_stub = WebMock::Builder.build(endpoint.verb, uri, {}, &callback)
67
-
68
- StubRegistry.record(service, endpoint, webmock_stub)
69
- ::WebMock::StubRegistry.instance.register_request_stub(webmock_stub)
70
- end
71
-
72
- # @api private
73
- # Used only for testing purposes
74
- def self.__stub_endpoint(service_id, endpoint_id, route_params = {})
75
- _service, endpoint, uri = StubRequests::URI.for_service_endpoint(service_id, endpoint_id, route_params)
76
- endpoint_stub = WebMock::Builder.build(endpoint.verb, uri)
77
-
78
- ::WebMock::StubRegistry.instance.register_request_stub(endpoint_stub)
79
- end
80
-
81
- #
82
- # @!attribute [rw] services
83
- # @return [Concurrent::Map<Symbol, Service>] a map with services
84
- attr_reader :services
85
-
86
- def initialize
87
- @services = Concurrent::Map.new
48
+ def size
49
+ keys.size
88
50
  end
51
+ alias count size
89
52
 
90
53
  #
91
54
  # Resets the map with registered services
@@ -96,18 +59,6 @@ module StubRequests
96
59
  services.clear
97
60
  end
98
61
 
99
- #
100
- # Required by Enumerable
101
- #
102
- #
103
- # @return [Concurrent::Map<Symbol, Service>] an map with services
104
- #
105
- # @yield used by Enumerable
106
- #
107
- def each(&block)
108
- services.each(&block)
109
- end
110
-
111
62
  #
112
63
  # Registers a service in the registry
113
64
  #
@@ -118,11 +69,11 @@ module StubRequests
118
69
  # @return [Service] the service that was just registered
119
70
  #
120
71
  def register(service_id, service_uri)
121
- if (service = find(service_id))
122
- StubRequests.logger.warn("Service already registered #{service}")
123
- return service
124
- end
125
- services[service_id] = Service.new(service_id, service_uri)
72
+ service = Service.new(service_id, service_uri)
73
+ StubRequests.logger.warn("Service already registered #{service}") if self[service_id]
74
+
75
+ self[service_id] = service
76
+ service
126
77
  end
127
78
 
128
79
  #
@@ -134,7 +85,7 @@ module StubRequests
134
85
  # @raise [ServiceNotFound] when the service was not removed
135
86
  #
136
87
  def remove(service_id)
137
- services.delete(service_id) || raise(ServiceNotFound, service_id)
88
+ delete(service_id) || raise(ServiceNotFound, service_id)
138
89
  end
139
90
 
140
91
  #
@@ -146,7 +97,7 @@ module StubRequests
146
97
  # @return [Service] the found service
147
98
  #
148
99
  def find(service_id)
149
- services[service_id]
100
+ self[service_id]
150
101
  end
151
102
 
152
103
  #
@@ -160,7 +111,7 @@ module StubRequests
160
111
  # @return [Service]
161
112
  #
162
113
  def find!(service_id)
163
- find(service_id) || raise(ServiceNotFound, service_id)
114
+ self[service_id] || raise(ServiceNotFound, service_id)
164
115
  end
165
116
  end
166
117
  end
@@ -15,6 +15,10 @@ module StubRequests
15
15
  # @since 0.1.2
16
16
  #
17
17
  class StubRegistry
18
+ # extend "Forwardable"
19
+ # @!parse extend Forwardable
20
+ extend Forwardable
21
+
18
22
  # includes "Singleton"
19
23
  # @!parse include Singleton
20
24
  include Singleton
@@ -22,50 +26,19 @@ module StubRequests
22
26
  # @!parse include Enumerable
23
27
  include Enumerable
24
28
 
25
- #
26
- # Records metrics about stubbed endpoints
27
- #
28
- #
29
- # @param [Service] service a Service
30
- # @param [Endpoint] endpoint an Endpoint
31
- # @param [WebMock::RequestStub] webmock_stub the stubbed webmock request
32
- #
33
- # @note the class method of record validates that
34
- # configuration option :collect_metrics is true.
35
- #
36
- # @return [EndpointStub] the stub that was recorded
37
- #
38
- def self.record(service, endpoint, webmock_stub)
39
- # Note: The class method v
40
- return unless StubRequests.config.record_metrics?
41
-
42
- instance.record(service, endpoint, webmock_stub)
43
- end
29
+ delegate [:each, :concat] => :stubs
44
30
 
45
31
  #
46
- # Mark a {RequestStub} as having responded
47
- #
48
- # @note Called when webmock responds successfully
49
- #
50
- # @param [WebMock::RequestStub] webmock_stub the stubbed webmock request
51
- #
52
- # @return [void]
53
- #
54
- def self.mark_as_responded(webmock_stub)
55
- instance.mark_as_responded(webmock_stub)
56
- end
57
-
58
- #
59
- # @!attribute [rw] services
60
- # @return [Concurrent::Array<Endpoint>] a map with stubbed endpoints
61
- attr_reader :endpoints
32
+ # @!attribute [r] stubs
33
+ # @return [Concurrent::Array] a collection of {RequestStub}
34
+ attr_reader :stubs
62
35
 
63
36
  #
64
37
  # Initialize a new registry
65
38
  #
66
39
  #
67
40
  def initialize
68
- @endpoints = Concurrent::Array.new
41
+ reset
69
42
  end
70
43
 
71
44
  #
@@ -74,37 +47,22 @@ module StubRequests
74
47
  #
75
48
  # @api private
76
49
  def reset
77
- endpoints.clear
78
- end
79
-
80
- #
81
- # Required by Enumerable
82
- #
83
- #
84
- # @return [Concurrent::Array<Endpoint>] an array with stubbed endpoints
85
- #
86
- # @yield used by Enumerable
87
- #
88
- def each(&block)
89
- endpoints.each(&block)
50
+ @stubs = Concurrent::Array.new
90
51
  end
91
52
 
92
53
  #
93
- # Records metrics about stubbed endpoints
54
+ # Records a WebMock::RequestStub as stubbed
94
55
  #
56
+ # @param [WebMock::RequestStub] webmock_stub <description>
95
57
  #
96
- # @param [Service] service a symbolic id of the service
97
- # @param [Endpoint] endpoint a string with a base_uri to the service
98
- # @param [WebMock::RequestStub] webmock_stub the stubbed request
58
+ # @return [RequestStub]
99
59
  #
100
- # @return [Service] the service that was just registered
101
- #
102
- def record(service, endpoint, webmock_stub)
103
- endpoint_stub = find_or_initialize_endpoint_stub(service, endpoint)
104
- endpoint_stub.record(webmock_stub)
60
+ def record(endpoint_id, webmock_stub)
61
+ return unless StubRequests.config.record_stubs?
105
62
 
106
- endpoints.push(endpoint_stub)
107
- endpoint_stub
63
+ request_stub = RequestStub.new(endpoint_id, webmock_stub)
64
+ concat([request_stub])
65
+ request_stub
108
66
  end
109
67
 
110
68
  #
@@ -117,10 +75,10 @@ module StubRequests
117
75
  # @return [void]
118
76
  #
119
77
  def mark_as_responded(webmock_stub)
120
- return unless (request_stub = find_request_stub(webmock_stub))
78
+ return unless (request_stub = find_by_webmock_stub(webmock_stub))
121
79
 
122
80
  request_stub.mark_as_responded
123
- CallbackRegistry.invoke_callbacks(request_stub)
81
+ CallbackRegistry.instance.invoke_callbacks(request_stub)
124
82
  request_stub
125
83
  end
126
84
 
@@ -132,24 +90,8 @@ module StubRequests
132
90
  #
133
91
  # @return [RequestStub] the request_stubbed matching the request stub
134
92
  #
135
- def find_request_stub(webmock_stub)
136
- map do |endpoint|
137
- endpoint.find_by(attribute: :request_stub, value: webmock_stub)
138
- end.compact.first
139
- end
140
-
141
- private
142
-
143
- def find_or_initialize_endpoint_stub(service, endpoint)
144
- find_endpoint_stub(service, endpoint) || initialize_endpoint_stub(service, endpoint)
145
- end
146
-
147
- def find_endpoint_stub(service, endpoint)
148
- find { |ep| ep.service_id == service.id && ep.endpoint_id == endpoint.id }
149
- end
150
-
151
- def initialize_endpoint_stub(service, endpoint)
152
- EndpointStub.new(service, endpoint)
93
+ def find_by_webmock_stub(webmock_stub)
94
+ find { |stub| stub.webmock_stub == webmock_stub }
153
95
  end
154
96
  end
155
97
  end
@@ -22,11 +22,6 @@ module StubRequests
22
22
  # @!parse extend API
23
23
  include API
24
24
 
25
- #
26
- # @!attribute [rw] logger
27
- # @return [Logger] the logger to use in the gem
28
- attr_accessor :logger
29
-
30
25
  #
31
26
  # Allows the gem to be configured
32
27
  #
@@ -50,6 +45,14 @@ module StubRequests
50
45
  @config ||= Configuration.new
51
46
  end
52
47
 
48
+ def logger
49
+ config.logger
50
+ end
51
+
52
+ def logger=(obj)
53
+ config.logger = obj
54
+ end
55
+
53
56
  #
54
57
  # The current version of the gem
55
58
  #
@@ -40,22 +40,5 @@ module StubRequests
40
40
  def self.safe_join(host, path)
41
41
  [host.chomp("/"), path.sub(%r{\A/}, "")].join("/")
42
42
  end
43
-
44
- #
45
- # UtilityFunction to construct the full URI for a service endpoint
46
- #
47
- # @param [Symbol] service_id the id of a service
48
- # @param [Symbol] endpoint_id the id of an endpoint
49
- # @param [Hash<Symbol>] route_params hash with route_params
50
- #
51
- # @return [Array<Service, Endpoint, String] the service, endpoint and full URI
52
- #
53
- def self.for_service_endpoint(service_id, endpoint_id, route_params)
54
- service = ServiceRegistry.instance.find!(service_id)
55
- endpoint = service.endpoints.find!(endpoint_id)
56
- uri = URI::Builder.build(service.uri, endpoint.path, route_params)
57
-
58
- [service, endpoint, uri]
59
- end
60
43
  end
61
44
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Abstraction over WebMock to reduce duplication
5
+ #
6
+ # @author Mikael Henriksson <mikael@zoolutions.se>
7
+ # @since 0.1.0
8
+ #
9
+ module StubRequests
10
+ #
11
+ # Module Utils provides a namespace for utility module
12
+ #
13
+ # @author Mikael Henriksson <mikael@zoolutions.se>
14
+ #
15
+ module Utils
16
+ #
17
+ # Provides convenience methods for hashes
18
+ #
19
+ # @author Mikael Henriksson <mikael@zoolutions.se>
20
+ #
21
+ module Fuzzy
22
+ #
23
+ # @return [Regexp] a pattern excluding all except alphanumeric
24
+ FILTER_REGEX = /(^\w\d)/.freeze
25
+ #
26
+ # Find strings that are similar
27
+ #
28
+ #
29
+ # @param [String] original a string to match
30
+ # @param [Array<String>] others an array of string to search
31
+ #
32
+ # @return [Array] Returns
33
+ #
34
+ def self.match(original, others)
35
+ matches = compute_distances(original, others).sort.reverse
36
+ filter_matches(matches.to_h)
37
+ end
38
+
39
+ # :nodoc:
40
+ def self.filter_matches(matches)
41
+ suggestions = matches.values
42
+ return suggestions if suggestions.size <= 3
43
+
44
+ matches.select { |distance, _| distance >= 0.7 }
45
+ .values
46
+ end
47
+
48
+ # :nodoc:
49
+ def self.compute_distances(original, others)
50
+ others.each_with_object([]) do |other, memo|
51
+ memo << [jaro_distance(original, other), other]
52
+ end
53
+ end
54
+
55
+ # :nodoc:
56
+ def self.jaro_distance(original, other)
57
+ JaroWinkler.jaro_distance(
58
+ normalize_string(original),
59
+ normalize_string(other),
60
+ StubRequests.config.jaro_options,
61
+ )
62
+ end
63
+
64
+ # :nodoc:
65
+ def self.normalize_string(value)
66
+ value.to_s.gsub(FILTER_REGEX, "")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -9,5 +9,5 @@
9
9
  module StubRequests
10
10
  #
11
11
  # @return [String] a version string
12
- VERSION = "0.1.9"
12
+ VERSION = "0.1.10"
13
13
  end
@@ -21,32 +21,25 @@ module StubRequests
21
21
  # @since 0.1.2
22
22
  #
23
23
  class Builder
24
- include HashUtil
25
-
26
24
  #
27
25
  # Builds and registers a WebMock::RequestStub
28
26
  #
29
27
  #
30
28
  # @param [Symbol] verb a HTTP verb/method
31
29
  # @param [String] uri a URI to call
32
- # @param [Hash<Symbol>] options request/response options for Webmock::RequestStub
33
30
  #
34
31
  # @yield a callback to eventually yield to the caller
35
32
  #
36
- # @return [WebMock::RequestStub] the registered stub
33
+ # @return [WebMock::RequestStub]
37
34
  #
38
- def self.build(verb, uri, options = {}, &callback)
39
- new(verb, uri, options, &callback).build
35
+ def self.build(verb, uri, &callback)
36
+ new(verb, uri, &callback).build
40
37
  end
41
38
 
42
39
  #
43
- # @!attribute [r] request_stub
40
+ # @!attribute [r] webmock_stub
44
41
  # @return [WebMock::RequestStub] a stubbed webmock request
45
- attr_reader :request_stub
46
- #
47
- # @!attribute [r] options
48
- # @return [Hash<Symbol>] options for the stub_request
49
- attr_reader :options
42
+ attr_reader :webmock_stub
50
43
  #
51
44
  # @!attribute [r] callback
52
45
  # @return [Block] call back when given a block
@@ -58,13 +51,11 @@ module StubRequests
58
51
  #
59
52
  # @param [Symbol] verb a HTTP verb/method
60
53
  # @param [String] uri a URI to call
61
- # @param [Hash<Symbol>] options request/response options for Webmock::RequestStub
62
54
  #
63
55
  # @yield a block to eventually yield to the caller
64
56
  #
65
- def initialize(verb, uri, options = {}, &callback)
66
- @request_stub = ::WebMock::RequestStub.new(verb, uri)
67
- @options = options
57
+ def initialize(verb, uri, &callback)
58
+ @webmock_stub = ::WebMock::RequestStub.new(verb, uri)
68
59
  @callback = callback
69
60
  end
70
61
 
@@ -75,41 +66,8 @@ module StubRequests
75
66
  # @return [WebMock::RequestStub] the registered stub
76
67
  #
77
68
  def build
78
- if callback.present?
79
- Docile.dsl_eval(request_stub, &callback)
80
- else
81
- prepare_mock_request
82
- end
83
-
84
- request_stub
85
- end
86
-
87
- private
88
-
89
- def prepare_mock_request
90
- prepare_with
91
- prepare_to_return
92
- prepare_to_raise
93
- request_stub.to_timeout if options[:timeout]
94
- request_stub
95
- end
96
-
97
- def prepare_with
98
- HashUtil.compact(options[:request]) do |request_options|
99
- request_stub.with(request_options)
100
- end
101
- end
102
-
103
- def prepare_to_return
104
- HashUtil.compact(options[:response]) do |response_options|
105
- request_stub.to_return(response_options)
106
- end
107
- end
108
-
109
- def prepare_to_raise
110
- return unless (error = options[:error])
111
-
112
- request_stub.to_raise(*Array(error))
69
+ Docile.dsl_eval(webmock_stub, &callback) if callback.present?
70
+ webmock_stub
113
71
  end
114
72
  end
115
73
  end