stub_requests 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +27 -0
  5. data/gemfiles/webmock_2.3.gemfile.lock +1 -1
  6. data/gemfiles/webmock_3.5.gemfile.lock +1 -1
  7. data/gemfiles/webmock_develop.gemfile.lock +1 -1
  8. data/lib/stub_requests.rb +19 -14
  9. data/lib/stub_requests/api.rb +30 -16
  10. data/lib/stub_requests/argument_validation.rb +12 -12
  11. data/lib/stub_requests/endpoint.rb +12 -10
  12. data/lib/stub_requests/exceptions.rb +2 -6
  13. data/lib/stub_requests/metrics.rb +5 -4
  14. data/lib/stub_requests/metrics/{endpoint_stat.rb → endpoint.rb} +23 -22
  15. data/lib/stub_requests/metrics/registry.rb +25 -25
  16. data/lib/stub_requests/metrics/{stub_stat.rb → request.rb} +22 -13
  17. data/lib/stub_requests/observable.rb +62 -0
  18. data/lib/stub_requests/observable/registry.rb +152 -0
  19. data/lib/stub_requests/observable/subscription.rb +58 -0
  20. data/lib/stub_requests/property.rb +9 -3
  21. data/lib/stub_requests/property/validator.rb +4 -4
  22. data/lib/stub_requests/registration.rb +87 -0
  23. data/lib/stub_requests/registration/endpoint.rb +107 -0
  24. data/lib/stub_requests/registration/endpoints.rb +156 -0
  25. data/lib/stub_requests/registration/registry.rb +112 -0
  26. data/lib/stub_requests/registration/service.rb +85 -0
  27. data/lib/stub_requests/uri.rb +1 -1
  28. data/lib/stub_requests/version.rb +1 -1
  29. data/lib/tasks/changelog.rake +11 -2
  30. data/update_docs.sh +33 -0
  31. metadata +13 -8
  32. data/bin/update_docs.sh +0 -16
  33. data/lib/stub_requests/endpoint_registry.rb +0 -159
  34. data/lib/stub_requests/service.rb +0 -77
  35. data/lib/stub_requests/service_registry.rb +0 -104
@@ -39,8 +39,8 @@ module StubRequests
39
39
  # Define property methods for the name
40
40
  #
41
41
  # @param [Symbol] name the name of the property
42
- # @param [Object] type: the expected type of the property
43
- # @param [Hash<Symbol>] **options a hash with options
42
+ # @param [Object] type the expected type of the property
43
+ # @param [Hash<Symbol>] options a hash with options
44
44
  # @option options [Object] :default a default value for the property
45
45
  #
46
46
  # @return [Object] the whatever
@@ -55,6 +55,7 @@ module StubRequests
55
55
  end
56
56
  end
57
57
 
58
+ # @api private
58
59
  def normalize_type(type, **options)
59
60
  type_array = Array(type)
60
61
  return type_array unless (default = options[:default])
@@ -62,6 +63,7 @@ module StubRequests
62
63
  type_array.concat([default.class]).flatten.uniq
63
64
  end
64
65
 
66
+ # @api private
65
67
  def define_property(name, type, default)
66
68
  property_reader(name)
67
69
  property_predicate(name)
@@ -70,6 +72,7 @@ module StubRequests
70
72
  set_property_defined(name, type, default)
71
73
  end
72
74
 
75
+ # @api private
73
76
  def property_reader(name)
74
77
  silence_redefinition_of_method(name.to_s)
75
78
  redefine_method(name) do
@@ -77,6 +80,7 @@ module StubRequests
77
80
  end
78
81
  end
79
82
 
83
+ # @api private
80
84
  def property_predicate(name)
81
85
  silence_redefinition_of_method("#{name}?")
82
86
  redefine_method("#{name}?") do
@@ -84,13 +88,15 @@ module StubRequests
84
88
  end
85
89
  end
86
90
 
91
+ # @api private
87
92
  def property_writer(name, type)
88
93
  redefine_method("#{name}=") do |obj|
89
- validate! name, obj, is_a: type
94
+ validate! name: name, value: obj, type: type
90
95
  instance_variable_set("@#{name}", obj)
91
96
  end
92
97
  end
93
98
 
99
+ # @api private
94
100
  def set_property_defined(name, type, default)
95
101
  properties[name] = { type: type, default: default }
96
102
  end
@@ -68,10 +68,10 @@ module StubRequests
68
68
  # @param [Hash] properties the list of currently defined properties
69
69
  #
70
70
  # :reek:LongParameterList
71
- def initialize(name, type, default, properties)
72
- @name = name
71
+ def initialize(name, type, default = nil, properties = {})
73
72
  @type = Array(type).flatten
74
73
  @default = default
74
+ @name = name
75
75
  @properties = properties
76
76
  end
77
77
 
@@ -101,7 +101,7 @@ module StubRequests
101
101
  # @return [void]
102
102
  #
103
103
  def validate_name
104
- validate! :name, name, is_a: Symbol
104
+ validate! name: :name, value: name, type: Symbol
105
105
  end
106
106
 
107
107
  #
@@ -115,7 +115,7 @@ module StubRequests
115
115
  def validate_default
116
116
  return unless default || default.is_a?(FalseClass)
117
117
 
118
- validate! :default, default, is_a: type
118
+ validate! name: :default, value: default, type: type
119
119
  end
120
120
 
121
121
  #
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StubRequests
4
+ #
5
+ # Module Registration handles registration of stubbed endpoints and services
6
+ #
7
+ # @author Mikael Henriksson <mikael@zoolutions.se>
8
+ # @since 0.1.3
9
+ #
10
+ module Registration
11
+ # Register a service in the service registry
12
+ #
13
+ #
14
+ # @param [Symbol] service_id a descriptive id for the service
15
+ # @param [Symbol] service_uri the uri used to call the service
16
+ #
17
+ # @example Register a service with endpoints
18
+ # register_service(:documents, "https://company.com/api/v1") do
19
+ # register(:show, :get, "documents/:id")
20
+ # register(:index, :get, "documents")
21
+ # register(:create, :post, "documents")
22
+ # register(:update, :patch, "documents/:id")
23
+ # register(:destroy, :delete, "documents/:id")
24
+ # end
25
+ #
26
+ # @return [Service] a new service or a previously registered service
27
+ #
28
+ def self.register_service(service_id, service_uri, &block)
29
+ service = Registry.instance.register(service_id, service_uri)
30
+ Docile.dsl_eval(service.endpoints, &block) if block.present?
31
+ service
32
+ end
33
+
34
+ #
35
+ # Stub a request to a registered service endpoint
36
+ #
37
+ #
38
+ # @param [Symbol] service_id the id of a registered service
39
+ # @param [Symbol] endpoint_id the id of a registered endpoint
40
+ # @param [Hash<Symbol>] uri_replacements a list of URI replacements
41
+ # @param [Hash<Symbol>] options
42
+ # @option options [optional, Hash<Symbol>] :request webmock request options
43
+ # @option options [optional, Hash<Symbol>] :response webmock response options
44
+ # @option options [optional, Array, Exception, StandardError, String] :error webmock error to raise
45
+ # @option options [optional, TrueClass] :timeout set to true to raise some kind of timeout error
46
+ #
47
+ # @note the kind of timeout error raised by webmock is depending on the HTTP client used
48
+ #
49
+ # @example Stub a request to a registered service endpoint
50
+ # register_stub(
51
+ # :google_api,
52
+ # :get_map_location,
53
+ # {}, # No URI replacements needed for this endpoint
54
+ # { request: { headers: { "Accept" => "application/json" }}},
55
+ # { response: { body: { id: "abyasdjasd", status: "successful" }}}
56
+ # )
57
+ #
58
+ # @example Stub a request to a registered service endpoint using block version
59
+ # register_stub(:documents, :index) do
60
+ # with(headers: { "Accept" => "application/json" }}})
61
+ # to_return(body: "No content", status: 204)
62
+ # end
63
+ #
64
+ # @see #stub_http_request
65
+ # @return [WebMock::RequestStub] a mocked request
66
+ #
67
+ # :reek:UtilityFunction
68
+ # :reek:LongParameterList { max_params: 5 }
69
+ def self.stub_endpoint(service_id, endpoint_id, uri_replacements = {}, options = {}, &callback)
70
+ service, endpoint, uri = StubRequests::URI.for_service_endpoint(service_id, endpoint_id, uri_replacements)
71
+ endpoint_stub = WebMock::Builder.build(endpoint.verb, uri, options, &callback)
72
+
73
+ Metrics.record(service, endpoint, endpoint_stub)
74
+ ::WebMock::StubRegistry.instance.register_request_stub(endpoint_stub)
75
+ end
76
+
77
+ # @api private
78
+ # Used only for testing purposes
79
+ # :reek:LongParameterList { max_params: 4 }
80
+ def self.__stub_endpoint(service_id, endpoint_id, uri_replacements = {}, options = {})
81
+ _service, endpoint, uri = StubRequests::URI.for_service_endpoint(service_id, endpoint_id, uri_replacements)
82
+ endpoint_stub = WebMock::Builder.build(endpoint.verb, uri, options)
83
+
84
+ ::WebMock::StubRegistry.instance.register_request_stub(endpoint_stub)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,107 @@
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 Registration provides registration of stubbed endpoints and services
12
+ #
13
+ # @author Mikael Henriksson <mikael@zoolutions.se>
14
+ # @since 0.1.3
15
+ #
16
+ module Registration
17
+ #
18
+ # Class Endpoint provides registration of stubbed endpoints
19
+ #
20
+ # @author Mikael Henriksson <mikael@zoolutions.se>
21
+ #
22
+ class Endpoint
23
+ extend Forwardable
24
+
25
+ include Comparable
26
+ include Property
27
+
28
+ # Delegate id, uri and endpoints to service
29
+ delegate [:id, :uri, :endpoints] => :service
30
+ #
31
+ # @!attribute [rw] id
32
+ # @return [Symbol] the id of the endpoint
33
+ property :id, type: Symbol
34
+ #
35
+ # @!attribute [rw] verb
36
+ # @return [Symbol] a HTTP verb
37
+ property :verb, type: Symbol
38
+ #
39
+ # @!attribute [rw] uri_template
40
+ # @return [String] a string template for the endpoint
41
+ property :uri_template, type: String
42
+ #
43
+ # @!attribute [rw] options
44
+ # @see
45
+ # @return [Hash<Symbol>] a Hash with default request/response options
46
+ property :options, type: Hash, default: {}
47
+
48
+ #
49
+ # An endpoint for a specific {StubRequests::Registration::Service}
50
+ #
51
+ # @param [Symbol] endpoint_id a descriptive id for the endpoint
52
+ # @param [Symbol] verb a HTTP verb
53
+ # @param [String] uri_template how to reach the endpoint
54
+ # @param [optional, Hash<Symbol>] options
55
+ # @option options [optional, Hash<Symbol>] :request for request_stub.with
56
+ # @option options [optional, Hash<Symbol>] :response for request_stub.to_return
57
+ # @option options [optional, Array, Exception, StandardError, String] :error for request_stub.to_raise
58
+ # @option options [optional, TrueClass] :timeout for request_stub.to_timeout
59
+ #
60
+ def initialize(endpoint_id, verb, uri_template, options = {})
61
+ self.id = endpoint_id
62
+ self.verb = verb
63
+ self.uri_template = uri_template
64
+ self.options = options
65
+ end
66
+
67
+ #
68
+ # Updates this endpoint
69
+ #
70
+ # @param [Symbol] verb a HTTP verb
71
+ # @param [String] uri_template how to reach the endpoint
72
+ # @param [optional, Hash<Symbol>] options
73
+ # @option options [optional, Hash<Symbol>] :request for request_stub.with
74
+ # @option options [optional, Hash<Symbol>] :response for request_stub.to_return
75
+ # @option options [optional, Array, Exception, StandardError, String] :error for request_stub.to_raise
76
+ # @option options [optional, TrueClass] :timeout for request_stub.to_timeout
77
+ #
78
+ # @return [Registration::Endpoint] returns the updated endpoint
79
+ #
80
+ def update(verb, uri_template, options)
81
+ self.verb = verb
82
+ self.uri_template = uri_template
83
+ self.options = options
84
+ self
85
+ end
86
+
87
+ def <=>(other)
88
+ id <=> other.id
89
+ end
90
+
91
+ def hash
92
+ [id, self.class].hash
93
+ end
94
+
95
+ alias eql? ==
96
+
97
+ #
98
+ # Returns a descriptive string of this endpoint
99
+ #
100
+ # @return [String]
101
+ #
102
+ def to_s
103
+ "#<#{self.class} id=:#{id} verb=:#{verb} uri_template='#{uri_template}'>"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,156 @@
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 Registration provides registration of stubbed endpoints and services
12
+ #
13
+ # @author Mikael Henriksson <mikael@zoolutions.se>
14
+ # @since 0.1.3
15
+ #
16
+ module Registration
17
+ #
18
+ # Class Endpoints manages a collection of endpoints
19
+ #
20
+ # @author Mikael Henriksson <mikael@zoolutions.se>
21
+ #
22
+ class Endpoints
23
+ include Enumerable
24
+
25
+ #
26
+ # @!attribute [rw] endpoints
27
+ # @return [Concurrent::Map<Symbol, Endpoint>] a map with endpoints
28
+ attr_reader :endpoints
29
+
30
+ def initialize
31
+ @endpoints = Concurrent::Map.new
32
+ end
33
+
34
+ #
35
+ # Required by Enumerable
36
+ #
37
+ # @return [Concurrent::Map<Symbol, Service>] a map with endpoints
38
+ #
39
+ # @yield used by Enumerable
40
+ #
41
+ def each(&block)
42
+ endpoints.each(&block)
43
+ end
44
+
45
+ #
46
+ # Registers an endpoint in the collection
47
+ #
48
+ # @param [Symbol] endpoint_id the id of this Endpoint
49
+ # @param [Symbol] verb a HTTP verb
50
+ # @param [String] uri_template the URI to reach the endpoint
51
+ # @param [optional, Hash<Symbol>] options default options
52
+ #
53
+ # @return [Endpoint]
54
+ #
55
+ # :reek:LongParameterList { max_params: 4 }
56
+ def register(endpoint_id, verb, uri_template, options = {})
57
+ endpoint =
58
+ if (endpoint = find(endpoint_id))
59
+ StubRequests.logger.warn("Endpoint already registered: #{endpoint}")
60
+ endpoint.update(verb, uri_template, options)
61
+ else
62
+ Endpoint.new(endpoint_id, verb, uri_template, options)
63
+ end
64
+
65
+ endpoints[endpoint.id] = endpoint
66
+ endpoint
67
+ end
68
+
69
+ #
70
+ # Updates an endpoint
71
+ #
72
+ # @param [Symbol] endpoint_id the id of the endpoint
73
+ # @param [Symbol] verb a HTTP verb
74
+ # @param [String] uri_template how to reach the endpoint
75
+ # @param [optional, Hash<Symbol>] options
76
+ # @option options [optional, Hash<Symbol>] :request request options
77
+ # @option options [optional, Hash<Symbol>] :response options
78
+ # @option options [optional, Array, Exception, StandardError, String] :error to raise
79
+ # @option options [optional, TrueClass] :timeout raise a timeout error?
80
+ #
81
+ # @raise [EndpointNotFound] when the endpoint couldn't be found
82
+ #
83
+ # @return [Endpoint] returns the updated endpoint
84
+ #
85
+ # :reek:LongParameterList { max_params: 4 }
86
+ def update(endpoint_id, verb, uri_template, options)
87
+ endpoint = find!(endpoint_id)
88
+ endpoint.update(verb, uri_template, options)
89
+ end
90
+
91
+ #
92
+ # Removes an endpoint from the collection
93
+ #
94
+ # @param [Symbol] endpoint_id the id of the endpoint, `:file_service`
95
+ #
96
+ # @return [Endpoint] the endpoint that was removed
97
+ #
98
+ def remove(endpoint_id)
99
+ endpoints.delete(endpoint_id)
100
+ end
101
+
102
+ #
103
+ # Fetches an endpoint from the collection
104
+ #
105
+ # @param [<type>] endpoint_id <description>
106
+ #
107
+ # @return [Endpoint]
108
+ #
109
+ def find(endpoint_id)
110
+ endpoints[endpoint_id]
111
+ end
112
+
113
+ #
114
+ # Fetches an endpoint from the collection or raises an error
115
+ #
116
+ # @param [Symbol] endpoint_id the id of the endpoint
117
+ #
118
+ # @raise [EndpointNotFound] when an endpoint couldn't be found
119
+ #
120
+ # @return [Endpoint, nil]
121
+ #
122
+ def find!(endpoint_id)
123
+ find(endpoint_id) || raise(EndpointNotFound, "Couldn't find an endpoint with id=:#{endpoint_id}")
124
+ end
125
+
126
+ #
127
+ # Returns a descriptive string with all endpoints in the collection
128
+ #
129
+ # @return [String]
130
+ #
131
+ def to_s
132
+ [
133
+ +"#<#{self.class} endpoints=",
134
+ +endpoints_string,
135
+ +">",
136
+ ].join("")
137
+ end
138
+
139
+ #
140
+ # Returns a nicely formatted string with an array of endpoints
141
+ #
142
+ #
143
+ # @return [<type>] <description>
144
+ #
145
+ def endpoints_string
146
+ "[#{endpoints_as_string}]"
147
+ end
148
+
149
+ private
150
+
151
+ def endpoints_as_string
152
+ endpoints.values.map(&:to_s).join(",") if endpoints.size.positive?
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,112 @@
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 Registration provides registration of stubbed endpoints and services
12
+ #
13
+ # @author Mikael Henriksson <mikael@zoolutions.se>
14
+ # @since 0.1.3
15
+ #
16
+ module Registration
17
+ #
18
+ # Class Registry provides registration of services
19
+ #
20
+ # @author Mikael Henriksson <mikael@zoolutions.se>
21
+ #
22
+ class Registry
23
+ include Singleton
24
+ include Enumerable
25
+
26
+ #
27
+ # @!attribute [rw] services
28
+ # @return [Concurrent::Map<Symbol, Service>] a map with services
29
+ attr_reader :services
30
+
31
+ def initialize
32
+ @services = Concurrent::Map.new
33
+ end
34
+
35
+ #
36
+ # Resets the map with registered services
37
+ #
38
+ #
39
+ # @api private
40
+ def reset
41
+ services.clear
42
+ end
43
+
44
+ #
45
+ # Required by Enumerable
46
+ #
47
+ #
48
+ # @return [Concurrent::Map<Symbol, Service>] an map with services
49
+ #
50
+ # @yield used by Enumerable
51
+ #
52
+ def each(&block)
53
+ services.each(&block)
54
+ end
55
+
56
+ #
57
+ # Registers a service in the registry
58
+ #
59
+ #
60
+ # @param [Symbol] service_id a symbolic id of the service
61
+ # @param [String] service_uri a string with a base_uri to the service
62
+ #
63
+ # @return [Registration::Service] the service that was just registered
64
+ #
65
+ def register(service_id, service_uri)
66
+ if (service = find(service_id))
67
+ StubRequests.logger.warn("Service already registered #{service}")
68
+ raise ServiceHaveEndpoints, service if service.endpoints?
69
+ end
70
+ services[service_id] = Service.new(service_id, service_uri)
71
+ end
72
+
73
+ #
74
+ # Removes a service from the registry
75
+ #
76
+ #
77
+ # @param [Symbol] service_id the service_id to remove
78
+ #
79
+ # @raise [ServiceNotFound] when the service was not removed
80
+ #
81
+ def remove(service_id)
82
+ services.delete(service_id) || raise(ServiceNotFound, service_id)
83
+ end
84
+
85
+ #
86
+ # Fetches a service from the registry
87
+ #
88
+ #
89
+ # @param [Symbol] service_id id of the service to remove
90
+ #
91
+ # @return [Registration::Service] the found service
92
+ #
93
+ def find(service_id)
94
+ services[service_id]
95
+ end
96
+
97
+ #
98
+ # Fetches a service from the registry or raises {ServiceNotFound}
99
+ #
100
+ #
101
+ # @param [Symbol] service_id the id of a service
102
+ #
103
+ # @raise [ServiceNotFound] when an endpoint couldn't be found
104
+ #
105
+ # @return [Registration::Service]
106
+ #
107
+ def find!(service_id)
108
+ find(service_id) || raise(ServiceNotFound, service_id)
109
+ end
110
+ end
111
+ end
112
+ end