flipp-mockserver-client 0.1.0

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