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,10 +13,12 @@ module StubRequests
13
13
  # @author Mikael Henriksson <mikael@zoolutions.se>
14
14
  #
15
15
  class Endpoint
16
- extend Forwardable
17
-
16
+ # includes "Comparable"
17
+ # @!parse include Comparable
18
18
  include Comparable
19
- include Property
19
+ # includes "Concerns::Property"
20
+ # @!parse include Concerns::Property
21
+ include Concerns::Property
20
22
  #
21
23
  # @!attribute [rw] id
22
24
  # @return [Symbol] the id of the endpoint
@@ -29,49 +31,46 @@ module StubRequests
29
31
  # @!attribute [rw] path
30
32
  # @return [String] a string template for the endpoint
31
33
  property :path, type: String
32
-
33
34
  #
34
- # @!attribute [rw] service
35
- # @see
36
- # @return [Service] a service
37
- attr_reader :service
38
-
35
+ # @!attribute [rw] uri
36
+ # @return [String] the full uri for the endpoint
37
+ attr_reader :uri
39
38
  #
40
39
  # @!attribute [rw] service_id
41
40
  # @see
42
41
  # @return [Symbol] the id of the service
43
42
  attr_reader :service_id
44
-
45
43
  #
46
44
  # @!attribute [rw] service_uri
47
45
  # @see
48
46
  # @return [String] a service's base URI
49
47
  attr_reader :service_uri
50
-
51
48
  #
52
- # @!attribute [rw] options
49
+ # @!attribute [rw] route_params
53
50
  # @see
54
51
  # @return [Array<Symbol>] an array with required route params
55
52
  attr_reader :route_params
56
53
 
57
54
  #
58
- # An endpoint for a specific {StubRequests::Service}
55
+ # Initialize an endpoint for a specific {Service}
59
56
  #
60
57
  #
61
- # @param [Service] service a registered service
62
58
  # @param [Symbol] endpoint_id a descriptive id for the endpoint
59
+ # @param [Symbol] service_id the id of a registered service
60
+ # @param [String] service_uri the uri of a registered service
63
61
  # @param [Symbol] verb a HTTP verb
64
62
  # @param [String] path how to reach the endpoint
65
63
  #
66
- def initialize(service, endpoint_id, verb, path)
67
- self.id = endpoint_id
68
- self.verb = verb
69
- self.path = path
64
+ def initialize(endpoint_id:, service_id:, service_uri:, verb:, path:)
65
+ self.id = endpoint_id
66
+ self.verb = verb
67
+ self.path = path
70
68
 
71
- @service = service
72
- @service_id = service.id
73
- @service_uri = service.uri
69
+ @service_id = service_id
70
+ @service_uri = service_uri
71
+ @uri = URI.safe_join(service_uri, path)
74
72
  @route_params = URI.route_params(path)
73
+ @stubs = Concurrent::Array.new
75
74
  end
76
75
 
77
76
  #
@@ -84,8 +83,8 @@ module StubRequests
84
83
  # @return [Endpoint] returns the updated endpoint
85
84
  #
86
85
  def update(verb, path)
87
- self.verb = verb
88
- self.path = path
86
+ @verb = verb
87
+ @path = path
89
88
  self
90
89
  end
91
90
 
@@ -0,0 +1,157 @@
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
+ # Class Endpoints manages a collection of endpoints
12
+ #
13
+ # @author Mikael Henriksson <mikael@zoolutions.se>
14
+ #
15
+ class EndpointRegistry
16
+ # extend "Forwardable"
17
+ # @!parse extend Forwardable
18
+ extend Forwardable
19
+
20
+ # includes "Singleton"
21
+ # @!parse include Singleton
22
+ include Singleton
23
+ # includes "Enumerable"
24
+ # @!parse include Enumerable
25
+ include Enumerable
26
+
27
+ delegate [:each, :[], :[]=, :delete, :keys, :values] => :endpoints
28
+
29
+ #
30
+ # Return all endpoints for a service
31
+ #
32
+ # @param [Symbol] service_id the id of a registered service
33
+ #
34
+ # @return [Array<Endpoint>] the endpoints for the service
35
+ #
36
+ def self.[](service_id)
37
+ instance.values.select { |ep| ep.service_id == service_id }
38
+ end
39
+
40
+ #
41
+ # @!attribute [rw] endpoints
42
+ # @return [Concurrent::Map<Symbol, Endpoint>] a map with endpoints
43
+ attr_reader :endpoints
44
+
45
+ #
46
+ # Initialize is used by Singleton
47
+ #
48
+ #
49
+ def initialize
50
+ reset
51
+ end
52
+
53
+ #
54
+ # Resets the endpoints array (used for testing)
55
+ #
56
+ #
57
+ def reset
58
+ @endpoints = Concurrent::Map.new
59
+ end
60
+
61
+ #
62
+ # The size of the endpoints array
63
+ #
64
+ #
65
+ # @return [Integer]
66
+ #
67
+ def size
68
+ keys.size
69
+ end
70
+ alias count size
71
+
72
+ #
73
+ # Registers an endpoint in the collection
74
+ #
75
+ # @param [Endpoint] endpoint the endpoint to register
76
+ #
77
+ # @return [Endpoint]
78
+ #
79
+ def register(endpoint)
80
+ StubRequests.logger.warn("Endpoint already registered: #{endpoint}") if find(endpoint)
81
+
82
+ self[endpoint.id] = endpoint
83
+ endpoint
84
+ end
85
+
86
+ #
87
+ # Fetches an endpoint from the collection or raises an error
88
+ #
89
+ #
90
+ # @param [Symbol] endpoint_id the id of the endpoint
91
+ #
92
+ # @raise [EndpointNotFound] when an endpoint couldn't be found
93
+ #
94
+ # @return [Endpoint]
95
+ #
96
+ def find!(endpoint_id)
97
+ endpoint = find(endpoint_id)
98
+ return endpoint if endpoint
99
+
100
+ raise EndpointNotFound, id: endpoint_id, suggestions: suggestions(endpoint_id)
101
+ end
102
+
103
+ #
104
+ # Fetches an endpoint from the collection or raises an error
105
+ #
106
+
107
+ # @param [Symbol] endpoint_id the id of the endpoint
108
+ #
109
+ # @return [Endpoint, nil]
110
+
111
+ def find(endpoint_id)
112
+ endpoint_id = endpoint_id.id if endpoint_id.is_a?(Endpoint)
113
+ self[endpoint_id]
114
+ end
115
+
116
+ #
117
+ # Returns an array of potential alternatives
118
+ #
119
+ # @param [Symbol] endpoint_id the id of an endpoint
120
+ #
121
+ # @return [Array<Symbol>] an array of endpoint_id's
122
+ #
123
+ def suggestions(endpoint_id)
124
+ Utils::Fuzzy.match(endpoint_id, keys)
125
+ end
126
+
127
+ #
128
+ # Returns a descriptive string with all endpoints in the collection
129
+ #
130
+ #
131
+ # @return [String]
132
+ #
133
+ def to_s
134
+ [
135
+ +"#<#{self.class} endpoints=",
136
+ +endpoints_string,
137
+ +">",
138
+ ].join("")
139
+ end
140
+
141
+ #
142
+ # Returns a nicely formatted string with an array of endpoints
143
+ #
144
+ #
145
+ # @return [<type>] <description>
146
+ #
147
+ def endpoints_string
148
+ "[#{endpoints_as_string}]"
149
+ end
150
+
151
+ private
152
+
153
+ def endpoints_as_string
154
+ endpoints.values.map(&:to_s).join(",") if endpoints.size.positive?
155
+ end
156
+ end
157
+ end
@@ -15,7 +15,34 @@ module StubRequests
15
15
  #
16
16
  # EndpointNotFound is raised when an endpoint cannot be found
17
17
  #
18
- class EndpointNotFound < Error; end
18
+ class EndpointNotFound < Error
19
+ attr_reader :id
20
+
21
+ def initialize(id:, suggestions: [])
22
+ @id = id
23
+ @suggestions = Array(suggestions).compact
24
+ error_message = [base_message, suggestions_message].join(".")
25
+ super(error_message)
26
+ end
27
+
28
+ def base_message
29
+ @base_message ||= "Couldn't find an endpoint with id=:#{id}"
30
+ end
31
+
32
+ def suggestions_message
33
+ return if suggestions.none?
34
+
35
+ @suggestions_message ||= " Did you mean one of the following? (#{suggestions_string})"
36
+ end
37
+
38
+ def suggestions
39
+ @suggestions.map { |sym| ":#{sym}" }
40
+ end
41
+
42
+ def suggestions_string
43
+ suggestions.join(", ")
44
+ end
45
+ end
19
46
 
20
47
  #
21
48
  # Class InvalidCallback is raised when a callback argument doesn't have the correct number of arguments
@@ -55,15 +82,6 @@ module StubRequests
55
82
  end
56
83
  end
57
84
 
58
- #
59
- # ServiceHaveEndpoints is raised to prevent overwriting a registered service's endpoints
60
- #
61
- class ServiceHaveEndpoints < StandardError
62
- def initialize(service)
63
- super("Service with id #{service.id} have already been registered. #{service}")
64
- end
65
- end
66
-
67
85
  #
68
86
  # ServiceNotFound is raised when a service cannot be found
69
87
  #
@@ -14,15 +14,20 @@ module StubRequests
14
14
  # @since 0.1.2
15
15
  #
16
16
  class RequestStub
17
- include Property
17
+ # extends "Forwardable"
18
+ # @!parse extend Forwardable
18
19
  extend Forwardable
19
20
 
21
+ # includes "Concerns::Property"
22
+ # @!parse include Concerns::Property
23
+ include Concerns::Property
24
+
20
25
  # Delegate service_id, endpoint_id, verb and path to endpoint
21
- delegate [:service_id, :endpoint_id, :verb, :path] => :endpoint
26
+ delegate [:service_id, :service_uri, :verb, :path] => :endpoint
22
27
  #
23
28
  # @!attribute [r] endpoint
24
- # @return [StubRequests::EndpointStub] a stubbed endpoint
25
- property :endpoint, type: StubRequests::EndpointStub
29
+ # @return [Symbol] the id of a registered {Endpoint}
30
+ property :endpoint_id, type: Symbol
26
31
  #
27
32
  # @!attribute [r] verb
28
33
  # @return [Symbol] a HTTP verb/method
@@ -30,11 +35,11 @@ module StubRequests
30
35
  #
31
36
  # @!attribute [r] uri
32
37
  # @return [String] the full URI for this endpoint
33
- property :uri, type: String
38
+ property :request_uri, type: String
34
39
  #
35
- # @!attribute [r] request_stub
40
+ # @!attribute [r] webmock_stub
36
41
  # @return [WebMock::RequestStub] a webmock stubbed request
37
- property :request_stub, type: WebMock::RequestStub
42
+ property :webmock_stub, type: WebMock::RequestStub
38
43
  #
39
44
  # @!attribute [r] recorded_at
40
45
  # @return [Time] the time this record was recorded
@@ -52,20 +57,30 @@ module StubRequests
52
57
  # Initialize a new Record
53
58
  #
54
59
  #
55
- # @param [Endpoint] endpoint a stubbed endpoint
56
- # @param [WebMock::RequestStub] request_stub the stubbed webmock request
60
+ # @param [Endpoint] endpoint_id the id of a stubbed endpoint
61
+ # @param [WebMock::RequestStub] webmock_stub the stubbed webmock request
57
62
  #
58
- def initialize(endpoint, request_stub)
59
- request_pattern = request_stub.request_pattern
60
- self.endpoint = endpoint
63
+ def initialize(endpoint_id, webmock_stub)
64
+ request_pattern = webmock_stub.request_pattern
65
+ self.endpoint_id = endpoint_id
61
66
  self.verb = request_pattern.method_pattern.to_s.to_sym
62
- self.uri = request_pattern.uri_pattern.to_s
63
- self.request_stub = request_stub
67
+ self.request_uri = request_pattern.uri_pattern.to_s
68
+ self.webmock_stub = webmock_stub
64
69
  self.recorded_at = Time.now
65
70
  self.recorded_from = RSpec.current_example.metadata[:location]
66
71
  @responded_at = nil # ByPass the validation for the initializer
67
72
  end
68
73
 
74
+ #
75
+ # Retrieve the endpoint for this request stub
76
+ #
77
+ #
78
+ # @return [Endpoint] <description>
79
+ #
80
+ def endpoint
81
+ EndpointRegistry.instance[endpoint_id]
82
+ end
83
+
69
84
  #
70
85
  # Marks this record as having responded
71
86
  #
@@ -13,8 +13,15 @@ module StubRequests
13
13
  # @author Mikael Henriksson <mikael@zoolutions.se>
14
14
  #
15
15
  class Service
16
+ # includes "Comparable"
17
+ # @!parse include Comparable
16
18
  include Comparable
17
- include Property
19
+ # includes "Concerns::Property"
20
+ # @!parse include Concerns::Property
21
+ include Concerns::Property
22
+ # includes "Concerns::RegisterVerb"
23
+ # @!parse include Concerns::RegisterVerb
24
+ include Concerns::RegisterVerb
18
25
 
19
26
  # @!attribute [rw] id
20
27
  # @return [Symbol] the id of the service
@@ -24,10 +31,6 @@ module StubRequests
24
31
  # @return [String] the base uri to the service
25
32
  property :uri, type: String
26
33
 
27
- # @!attribute [rw] endpoints
28
- # @return [Endpoints] a list with defined endpoints
29
- attr_reader :endpoints
30
-
31
34
  #
32
35
  # Initializes a new instance of a Service
33
36
  #
@@ -37,7 +40,26 @@ module StubRequests
37
40
  def initialize(service_id, service_uri)
38
41
  self.id = service_id
39
42
  self.uri = service_uri
40
- @endpoints = Endpoints.new(self)
43
+ end
44
+
45
+ #
46
+ # Register and endpoint for this service
47
+ #
48
+ # @param [Symbol] endpoint_id the id of the endpoint
49
+ # @param [Symbol] verb the HTTP verb/method
50
+ # @param [String] path the path to the endpoint
51
+ #
52
+ # @return [Endpoint] the endpoint that was registered
53
+ #
54
+ def register(endpoint_id, verb, path)
55
+ endpoint = Endpoint.new(
56
+ service_id: id,
57
+ service_uri: uri,
58
+ endpoint_id: endpoint_id,
59
+ verb: verb,
60
+ path: path,
61
+ )
62
+ EndpointRegistry.instance.register(endpoint)
41
63
  end
42
64
 
43
65
  #
@@ -49,6 +71,16 @@ module StubRequests
49
71
  endpoints.any?
50
72
  end
51
73
 
74
+ #
75
+ # The endpoints for this service
76
+ #
77
+ #
78
+ # @return [Array<Endpoints>]
79
+ #
80
+ def endpoints
81
+ EndpointRegistry[id]
82
+ end
83
+
52
84
  #
53
85
  # Returns a nicely formatted string with this service
54
86
  #
@@ -59,7 +91,7 @@ module StubRequests
59
91
  +"#<#{self.class}",
60
92
  +" id=#{id}",
61
93
  +" uri=#{uri}",
62
- +" endpoints=#{endpoints.endpoints_string}",
94
+ +" endpoints=#{endpoints_string}",
63
95
  +">",
64
96
  ].join("")
65
97
  end
@@ -73,5 +105,21 @@ module StubRequests
73
105
  end
74
106
 
75
107
  alias eql? ==
108
+
109
+ #
110
+ # Returns a nicely formatted string with an array of endpoints
111
+ #
112
+ #
113
+ # @return [String]
114
+ #
115
+ def endpoints_string
116
+ "[#{endpoints_as_string}]"
117
+ end
118
+
119
+ private
120
+
121
+ def endpoints_as_string
122
+ endpoints.map(&:to_s).join(",") if endpoints?
123
+ end
76
124
  end
77
125
  end