flipp-mockserver-client 0.1.0

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 +7 -0
  2. data/.gitignore +21 -0
  3. data/.rubocop.yml +7 -0
  4. data/Gemfile +4 -0
  5. data/README.md +182 -0
  6. data/Rakefile +10 -0
  7. data/bin/mockserver +9 -0
  8. data/lib/cli.rb +146 -0
  9. data/lib/mockserver-client.rb +17 -0
  10. data/lib/mockserver/abstract_client.rb +111 -0
  11. data/lib/mockserver/mock_server_client.rb +59 -0
  12. data/lib/mockserver/model/array_of.rb +85 -0
  13. data/lib/mockserver/model/body.rb +56 -0
  14. data/lib/mockserver/model/cookie.rb +36 -0
  15. data/lib/mockserver/model/delay.rb +34 -0
  16. data/lib/mockserver/model/enum.rb +47 -0
  17. data/lib/mockserver/model/expectation.rb +158 -0
  18. data/lib/mockserver/model/forward.rb +41 -0
  19. data/lib/mockserver/model/header.rb +43 -0
  20. data/lib/mockserver/model/parameter.rb +43 -0
  21. data/lib/mockserver/model/request.rb +77 -0
  22. data/lib/mockserver/model/response.rb +45 -0
  23. data/lib/mockserver/model/times.rb +61 -0
  24. data/lib/mockserver/proxy_client.rb +9 -0
  25. data/lib/mockserver/utility_methods.rb +59 -0
  26. data/lib/mockserver/version.rb +5 -0
  27. data/mockserver-client.gemspec +36 -0
  28. data/pom.xml +116 -0
  29. data/spec/fixtures/forward_mockserver.json +7 -0
  30. data/spec/fixtures/incorrect_login_response.json +20 -0
  31. data/spec/fixtures/post_login_request.json +22 -0
  32. data/spec/fixtures/register_expectation.json +50 -0
  33. data/spec/fixtures/retrieved_request.json +22 -0
  34. data/spec/fixtures/search_request.json +6 -0
  35. data/spec/fixtures/times_once.json +6 -0
  36. data/spec/integration/mock_client_integration_spec.rb +82 -0
  37. data/spec/mockserver/builder_spec.rb +136 -0
  38. data/spec/mockserver/mock_client_spec.rb +80 -0
  39. data/spec/mockserver/proxy_client_spec.rb +38 -0
  40. data/spec/spec_helper.rb +61 -0
  41. metadata +293 -0
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+ require_relative './model/expectation'
3
+ require_relative './abstract_client'
4
+ require_relative './utility_methods'
5
+
6
+ #
7
+ # The client used to interact with the mock server.
8
+ # @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
9
+ #
10
+ module MockServer
11
+ EXPECTATION_ENDPOINT = '/expectation'
12
+
13
+ # The client used to interact with the mock server.
14
+ class MockServerClient < AbstractClient
15
+ include Model
16
+ include UtilityMethods
17
+
18
+ # Registers an expectation with the mockserver
19
+ # @param expectation [Expectation] the expectation to create the request from
20
+ # @return [Object] the response from the register action
21
+ def register(expectation)
22
+ fail 'Expectation passed in is not valid type' unless expectation.is_a?(Expectation)
23
+ request = create_expectation_request(expectation)
24
+
25
+ logger.debug('Registering new expectation')
26
+ logger.debug("URL: #{EXPECTATION_ENDPOINT} Payload: #{request.to_hash}")
27
+
28
+ response = @base[EXPECTATION_ENDPOINT].put(request.to_json, content_type: :json)
29
+ logger.debug("Got register response: #{response.code}")
30
+ parse_string_to_json(response)
31
+ end
32
+
33
+ def register_expectations(expectations)
34
+ request_array = []
35
+ expectations.each { |x|
36
+ fail 'Expectation passed in is not valid type' unless x.is_a?(Expectation)
37
+ request = create_expectation_request(x)
38
+ request_array << request
39
+ }
40
+ logger.debug('Registering new array of expectations')
41
+ response = @base[EXPECTATION_ENDPOINT].put(request_array.to_json, content_type: :json)
42
+ logger.debug("Got register response: #{response.code}")
43
+ parse_string_to_json(response)
44
+ end
45
+
46
+ private
47
+
48
+ # Create an expecation request to send to the expectation endpoint of
49
+ # @param expectation [Expectation] the expectation to create the request from
50
+ # @return [Hash] a hash representing the request to use in registering an expectation with the mock server
51
+ # rubocop:disable Lint/LiteralInInterpolation
52
+ def create_expectation_request(expectation)
53
+ expectation_request = camelized_hash(expectation)
54
+ logger.debug("Expectation JSON: #{expectation_request.to_json}")
55
+ fail "You can only set either of #{[HTTP_RESPONSE, HTTP_FORWARD]}. But not both" if expectation_request[HTTP_RESPONSE] && expectation_request[HTTP_FORWARD]
56
+ expectation_request
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # The ArrayOf class stores instances of a given class only.
4
+ # It enforces this by intercepting the methods :<<, :[]= and :insert on the Array class
5
+ # and casts objects to the allowed type first. To use in your code, create a subclass of ArrayOf and override the child_class() method to return
6
+ # the class associated with the array.
7
+ #
8
+ # NOTE: You should use this class internally with the contract that you only call :<<. :[]= and :insert method
9
+ # to manipulate the array in use. Can easily be changed to have stricter rules, suffices for internal use in this gem.
10
+ #
11
+ # @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
12
+ #
13
+ module MockServer::Model
14
+ DEFAULT_MISSING_INDEX = -1_000_000_000
15
+
16
+ # The ArrayOf class stores instances of a given class only.
17
+ class ArrayOf < Array
18
+ alias_method :add_element, :<<
19
+ alias_method :set_element, :[]=
20
+ alias_method :insert_element, :insert
21
+
22
+ # Create an array from the elements passed in
23
+ def initialize(items)
24
+ items.each do |item|
25
+ self << item
26
+ end
27
+ end
28
+
29
+ # The class/type that this array stores
30
+ def child_class
31
+ fail 'Subclass should override method :child_class'
32
+ end
33
+
34
+ # Add the item to the array
35
+ # @param item [Object] an item of the type/class supported by this array
36
+ # @raise [Exception] if the item cannot be converted to the allowed class
37
+ def <<(item)
38
+ add_element(convert_to_child_class(item))
39
+ end
40
+
41
+ # Set the given item at the index
42
+ # @param index [Integer] the index for the new element
43
+ # @param item [Object] an item of the type/class supported by this array
44
+ # @raise [Exception] if the item cannot be converted to the allowed class
45
+ def []=(index, item)
46
+ set_element(index, convert_to_child_class(item))
47
+ end
48
+
49
+ # Adds the given item at the index and shifts elements forward
50
+ # @param index [Integer] the index for the new element
51
+ # @param item [Object] an item of the type/class supported by this array
52
+ # @raise [Exception] if the item cannot be converted to the allowed class
53
+ def insert(index, item)
54
+ insert_element(index, convert_to_child_class(item))
55
+ end
56
+
57
+ # Method to set the element at the specified index.
58
+ # Will insert at index if there is another object at the index; otherwise will update.
59
+ # If the special DEFAULT_MISSING_INDEX value is given, will insert at the end.
60
+ # @param index [Integer] the index for the new element
61
+ # @param item [Object] an item of the type/class supported by this array
62
+ # @raise [Exception] if the item cannot be converted to the allowed class
63
+ def set(index, item)
64
+ if index == DEFAULT_MISSING_INDEX
65
+ self << item
66
+ elsif self[index]
67
+ insert(index, item)
68
+ else
69
+ self[index] = item
70
+ end
71
+ end
72
+
73
+ # Cast item to target class
74
+ def convert_to_child_class(item)
75
+ if item && item.class != child_class
76
+ begin
77
+ item = child_class.new(item)
78
+ rescue Exception => e # rubocop:disable Lint/RescueException
79
+ raise "Failed to convert element: #{item} to required type #{child_class}. Error: #{e.message}"
80
+ end
81
+ end
82
+ item
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: UTF-8
2
+ require 'hashie'
3
+ require_relative './parameter'
4
+ require_relative './enum'
5
+
6
+ #
7
+ # A model for a a body in a request object.
8
+ # @author: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
9
+ #
10
+ module MockServer::Model
11
+ # An enum for body type
12
+ class BodyType < SymbolizedEnum
13
+ def allowed_values
14
+ [:STRING, :REGEX, :XPATH, :PARAMETERS, :BINARY]
15
+ end
16
+ end
17
+
18
+ # A model for a a body in a request object.
19
+ class Body < Hashie::Dash
20
+ include Hashie::Extensions::MethodAccess
21
+ include Hashie::Extensions::IgnoreUndeclared
22
+ include Hashie::Extensions::Coercion
23
+
24
+ property :type, required: true
25
+ property :value
26
+ property :parameters
27
+
28
+ coerce_key :type, BodyType
29
+ coerce_key :value, String
30
+ coerce_key :parameters, Parameters
31
+ end
32
+
33
+ # DSL methods related to body
34
+ module DSL
35
+ # For response object where body can only be a string
36
+ def body(value)
37
+ value
38
+ end
39
+
40
+ def exact(value)
41
+ Body.new(type: :STRING, value: value)
42
+ end
43
+
44
+ def regex(value)
45
+ Body.new(type: :REGEX, value: value)
46
+ end
47
+
48
+ def xpath(value)
49
+ Body.new(type: :XPATH, value: value)
50
+ end
51
+
52
+ def parameterized(*parameters)
53
+ Body.new(type: :PARAMETERS, parameters: parameters)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ require 'hashie'
3
+ require_relative './array_of'
4
+
5
+ #
6
+ # A class to model cookies in payloads.
7
+ # @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
8
+ #
9
+ module MockServer::Model
10
+ # Model for cookie
11
+ class Cookie < Hashie::Dash
12
+ include Hashie::Extensions::MethodAccess
13
+ include Hashie::Extensions::IgnoreUndeclared
14
+ include Hashie::Extensions::Coercion
15
+
16
+ property :name, required: true
17
+ property :value, required: true
18
+
19
+ coerce_key :name, String
20
+ coerce_key :value, String
21
+ end
22
+
23
+ # A collection that only stores cookies
24
+ class Cookies < ArrayOf
25
+ def child_class
26
+ Cookie
27
+ end
28
+ end
29
+
30
+ # DSL methods for cookie
31
+ module DSL
32
+ def cookie(key, value)
33
+ Cookie.new(name: key, value: value)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: UTF-8
2
+ require_relative './enum'
3
+
4
+ #
5
+ # A model for a delay in a response.
6
+ # @author: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
7
+ #
8
+ module MockServer::Model
9
+ # Enum for time unit
10
+ class TimeUnit < SymbolizedEnum
11
+ def allowed_values
12
+ [:NANOSECONDS, :MICROSECONDS, :MILLISECONDS, :SECONDS, :MINUTES, :HOURS, :DAYS]
13
+ end
14
+ end
15
+
16
+ # Model a delay object
17
+ class Delay < Hashie::Dash
18
+ include Hashie::Extensions::MethodAccess
19
+ include Hashie::Extensions::IgnoreUndeclared
20
+ include Hashie::Extensions::Coercion
21
+
22
+ property :time_unit, default: 'SECONDS'
23
+ property :value, required: true
24
+
25
+ coerce_key :time_unit, TimeUnit
26
+ end
27
+
28
+ # DSL methods related to delay model
29
+ module DSL
30
+ def delay_by(time_unit, value)
31
+ Delay.new(time_unit: time_unit, value: value)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # A class to model a Java-like Enum.
4
+ # To create an Enum extend this class and override :allowed_values method with allowed enum values.
5
+ #
6
+ # @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
7
+ #
8
+ module MockServer::Model
9
+ # Enum generic class
10
+ class Enum
11
+ # Create an instance of the enum from the value supplied
12
+ # @param supplied_value [Object] value used to create instance of an enum
13
+ # @raise [Exception] if the supplied value is not valid for this enum
14
+ def initialize(supplied_value)
15
+ supplied_value = pre_process_value(supplied_value)
16
+ fail "Supplied value: #{supplied_value} is not valid. Allowed values are: #{allowed_values.inspect}" unless allowed_values.include?(supplied_value)
17
+ @value = supplied_value
18
+ end
19
+
20
+ # @return [Array] a list of values allowed by this enum
21
+ def allowed_values
22
+ fail 'Override :allowed_values in Enum class'
23
+ end
24
+
25
+ # A pre-process hook for a value before it is stored
26
+ # @param value [Object] a value used to instantiate the enum
27
+ # @return [Object] the processed value. By default, a no-op implementation.
28
+ def pre_process_value(value)
29
+ value
30
+ end
31
+
32
+ # Override this for JSON representation
33
+ def to_s
34
+ @value.to_s
35
+ end
36
+ end
37
+
38
+ # Subclass of Enum that has a list of symbols as allowed values.
39
+ class SymbolizedEnum < Enum
40
+ # Pre-process the value passed in and convert to a symbol
41
+ # @param value [Object] a value used to instantiate the enum
42
+ # @return [Symbol] a symbolized version of the value passed in (first calls to_s)
43
+ def pre_process_value(value)
44
+ value.to_s.to_sym
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,158 @@
1
+ # encoding: UTF-8
2
+ require_relative './array_of'
3
+ require_relative './request'
4
+ require_relative './response'
5
+ require_relative './forward'
6
+ require_relative './times'
7
+ require_relative '../utility_methods'
8
+
9
+ #
10
+ # A class to model an expectation sent to the Mockserver instance.
11
+ # See http://www.mock-server.com/#create-expectations for details.
12
+ #
13
+ # @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
14
+ #
15
+ module MockServer
16
+ HTTP_REQUEST = 'httpRequest' unless const_defined?(:HTTP_REQUEST)
17
+ HTTP_RESPONSE = 'httpResponse' unless const_defined?(:HTTP_RESPONSE)
18
+ HTTP_FORWARD = 'httpForward' unless const_defined?(:HTTP_FORWARD)
19
+ HTTP_TIMES = 'times' unless const_defined?(:HTTP_TIMES)
20
+
21
+ # Models
22
+ module Model
23
+ # Expectation model
24
+ class Expectation
25
+ include MockServer::UtilityMethods
26
+ attr_accessor :times
27
+
28
+ # Creates an expectation from a hash
29
+ # @param payload [Hash] a hash representation of the expectation
30
+ def populate_from_payload(payload)
31
+ @request = payload[MockServer::HTTP_REQUEST]
32
+ @request = Request.new(symbolize_keys(@request)) if @request
33
+
34
+ @response = payload[MockServer::HTTP_RESPONSE]
35
+ @response = Response.new(symbolize_keys(@response)) if @response
36
+
37
+ @forward = payload[MockServer::HTTP_FORWARD]
38
+ @forward = Forward.new(symbolize_keys(@forward)) if @forward
39
+
40
+ @times = payload[MockServer::HTTP_TIMES]
41
+ @times = Times.new(symbolize_keys(@times)) if @times
42
+ end
43
+
44
+ # Method to setup the request on the expectation object
45
+ # @yieldparam [Request] the request that this expectation references
46
+ # @return [Expectation] this object according to the the builder pattern
47
+ def request(&_)
48
+ if block_given?
49
+ @request ||= Request.new
50
+ yield @request
51
+ end
52
+ @request
53
+ end
54
+
55
+ # Method to setup the response on the expectation object
56
+ # @yieldparam [Response] the response that this expectation references
57
+ # @return [Expectation] this object according to the the builder pattern
58
+ def response(&_)
59
+ if block_given?
60
+ @response ||= Response.new
61
+ yield @response
62
+ end
63
+ @response
64
+ end
65
+
66
+ # Method to setup the request on the expectation object
67
+ # @yieldparam [Forward] the forward object that this expectation references
68
+ # @return [Expectation] this object according to the the builder pattern
69
+ def forward(&_)
70
+ if block_given?
71
+ @forward ||= Forward.new
72
+ yield @forward
73
+ end
74
+ @forward
75
+ end
76
+
77
+ # Setter for request
78
+ # @param request [Request] a request object
79
+ def request=(request)
80
+ @request = Request.new(request)
81
+ end
82
+
83
+ # Setter for response
84
+ # @param response [Response] a response object
85
+ def response=(response)
86
+ @response = Response.new(response)
87
+ end
88
+
89
+ # Setter for forward
90
+ # @param forward [Forward] a forward object
91
+ def forward=(forward)
92
+ @forward = Forward.new(forward)
93
+ end
94
+
95
+ # Override to_json method
96
+ # @return [String] the json representation for this object
97
+ def to_json(*p)
98
+ to_hash.to_json(*p)
99
+ end
100
+
101
+ # Convert to hash
102
+ # @return [Hash] the hash representation for this object
103
+ def to_hash
104
+ {
105
+ MockServer::HTTP_REQUEST => @request,
106
+ MockServer::HTTP_RESPONSE => @response,
107
+ MockServer::HTTP_FORWARD => @forward,
108
+ MockServer::HTTP_TIMES => @times
109
+ }
110
+ end
111
+ end
112
+
113
+ # Class to store a list of mocks - useful for modeling retrieve endpoint result
114
+ class Expectations < ArrayOf
115
+ # Code is used to store HTTP status code returned from retrieve endpoint
116
+ attr_accessor :code
117
+
118
+ def child_class
119
+ Expectation
120
+ end
121
+ end
122
+
123
+ # DSL method for creating expectation
124
+ module DSL
125
+ def expectation(&_)
126
+ expectation = Expectation.new
127
+ yield expectation if block_given?
128
+ expectation
129
+ end
130
+
131
+ def expectation_from_json(payload)
132
+ expectation = Expectation.new
133
+ yield expectation if block_given?
134
+ expectation.populate_from_payload(payload)
135
+ expectation
136
+ end
137
+
138
+ def expectation_array_from_json(json_array)
139
+ expectation_array = []
140
+ json_array.each { |x|
141
+ e = expectation_from_json(x)
142
+ expectation_array << e
143
+ }
144
+ expectation_array
145
+ end
146
+
147
+ def add_header_to_expect_array(expectation_array, key, value)
148
+ expectations = []
149
+ expectation_array.each { |expectation|
150
+ expectation.request.headers << header(key, value)
151
+ expectations << expectation
152
+ }
153
+ expectations
154
+ end
155
+
156
+ end
157
+ end
158
+ end