stub_requests 0.1.9 → 0.1.10

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.
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