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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +7 -0
- data/Gemfile +4 -0
- data/README.md +182 -0
- data/Rakefile +10 -0
- data/bin/mockserver +9 -0
- data/lib/cli.rb +146 -0
- data/lib/mockserver-client.rb +17 -0
- data/lib/mockserver/abstract_client.rb +111 -0
- data/lib/mockserver/mock_server_client.rb +59 -0
- data/lib/mockserver/model/array_of.rb +85 -0
- data/lib/mockserver/model/body.rb +56 -0
- data/lib/mockserver/model/cookie.rb +36 -0
- data/lib/mockserver/model/delay.rb +34 -0
- data/lib/mockserver/model/enum.rb +47 -0
- data/lib/mockserver/model/expectation.rb +158 -0
- data/lib/mockserver/model/forward.rb +41 -0
- data/lib/mockserver/model/header.rb +43 -0
- data/lib/mockserver/model/parameter.rb +43 -0
- data/lib/mockserver/model/request.rb +77 -0
- data/lib/mockserver/model/response.rb +45 -0
- data/lib/mockserver/model/times.rb +61 -0
- data/lib/mockserver/proxy_client.rb +9 -0
- data/lib/mockserver/utility_methods.rb +59 -0
- data/lib/mockserver/version.rb +5 -0
- data/mockserver-client.gemspec +36 -0
- data/pom.xml +116 -0
- data/spec/fixtures/forward_mockserver.json +7 -0
- data/spec/fixtures/incorrect_login_response.json +20 -0
- data/spec/fixtures/post_login_request.json +22 -0
- data/spec/fixtures/register_expectation.json +50 -0
- data/spec/fixtures/retrieved_request.json +22 -0
- data/spec/fixtures/search_request.json +6 -0
- data/spec/fixtures/times_once.json +6 -0
- data/spec/integration/mock_client_integration_spec.rb +82 -0
- data/spec/mockserver/builder_spec.rb +136 -0
- data/spec/mockserver/mock_client_spec.rb +80 -0
- data/spec/mockserver/proxy_client_spec.rb +38 -0
- data/spec/spec_helper.rb +61 -0
- 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
|