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