mockserver-client 0.0.1

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2ZiZDY2MzcwMzQ5NGViNDc2YTQzYmY0NmYxMWI3ZjhkNGY5NzYyNQ==
5
+ data.tar.gz: !binary |-
6
+ OGVmMWM2ZWExZGVmZTk0YTU0MDI1MzU2ODM5YWNmMTNmYTY1M2NiMw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzNkZDY5NWE2YTNlMGQ5YjQ1YWNiYjY0MGRlNTllYmZmMzRmN2YwNDYyOGM4
10
+ ODA2YzFiNzY0MGZjOWVlYTYyZGRhNzRmMzZlYmRkMzk4NjU3N2EyYzc5OGNk
11
+ NDBkMWFmMmRkNTk0YTI4NTFiOGM3MjMzMTc4NzQ4MTFiNzc2ZmQ=
12
+ data.tar.gz: !binary |-
13
+ MGIyZDEwOTUwMjllYTc4YTgzNWEyYTdlMjAzZDg4MWUyMmExMDA3Nzg3ZTI1
14
+ ODNlNmJmYzcxODI4NjVkZWMxOTgzZGQyNmMxOGRiZGQzYmEyZWMwYWNhYzIw
15
+ MTMyMTk3ZTk5M2MzOTcwNjg4ZjliOTFlZWQ2OWM4MmY5ZTQ1MjQ=
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ tmp.log
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ Style/LineLength:
2
+ Max: 180
3
+ Style/ClassAndModuleChildren:
4
+ EnforcedStyle: compact
5
+ Style/CyclomaticComplexity:
6
+ Enabled: false
7
+ Lint/LiteralInInterpolation:
8
+ Enabled: false
9
+ Style/MethodLength:
10
+ Enabled: false
11
+ Style/FileName:
12
+ Exclude:
13
+ - '**/mockserver-client.rb'
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ script:
5
+ - bundle exec rake rubocop
6
+ - bundle exec rake spec
7
+ - bundle exec rake build
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Gem's dependencies in mockserver-client.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # Mockserver Client
2
+
3
+ [![Build Status](https://travis-ci.org/nayyara-samuel/mockserver-client.svg?branch=master)](https://travis-ci.org/nayyara-samuel/mockserver-client)
4
+
5
+ A Ruby client for [MockServer](http://www.mock-server.com) project. This client follows the Java client's fluent style closely by using Ruby blocks.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'mockserver-client'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install mockserver-client
20
+
21
+ ## Usage
22
+
23
+ The usage notes here compare the Java syntax with the Ruby DSL for the various actions the MockServer client/proxy client supports. The Ruby code here assumes you have included `MockServer` and `MockServer::Model::DSL` modules. So these lines should typically be at the top of your code:
24
+
25
+ ```ruby
26
+ include MockServer
27
+ include MockServer::Model::DSL
28
+ ```
29
+
30
+ ### Create Expectations
31
+
32
+ ##### Java
33
+ ```java
34
+
35
+ new MockServerClient("localhost", 9999)
36
+ .when(
37
+ request()
38
+ .withMethod("POST")
39
+ .withPath("/login")
40
+ .withQueryStringParameters(
41
+ new Parameter("returnUrl", "/account")
42
+ )
43
+ .withCookies(
44
+ new Cookie("sessionId", "2By8LOhBmaW5nZXJwcmludCIlMDAzMW")
45
+ )
46
+ .withBody("{username: 'foo', password: 'bar'}"),
47
+ Times.exactly(1)
48
+ )
49
+ .respond(
50
+ response()
51
+ .withStatusCode(401)
52
+ .withHeaders(
53
+ new Header("Content-Type", "application/json; charset=utf-8"),
54
+ new Header("Cache-Control", "public, max-age=86400")
55
+ )
56
+ .withBody("{ message: 'incorrect username and password combination' }")
57
+ .withDelay(new Delay(TimeUnit.SECONDS, 1))
58
+ );
59
+ ```
60
+
61
+ ##### Ruby
62
+
63
+ ```ruby
64
+
65
+ client = MockServerClient.new('localhost', 9999)
66
+ expectation = expectation do |expectation|
67
+ expectation.request do |request|
68
+ request.method = 'POST'
69
+ request.path = '/login'
70
+ request.query_parameters << parameter('returnUrl', '/account')
71
+ request.cookies = [cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')]
72
+ request.body = exact("{username: 'foo', password: 'bar'}")
73
+ end
74
+
75
+ expectation.response do |response|
76
+ response.status_code = 401
77
+ response.headers << header('Content-Type', 'application/json; charset=utf-8')
78
+ response.headers << header('Cache-Control', 'public, max-age=86400')
79
+ response.body = body("{ message: 'incorrect username and password combination' }")
80
+ response.delay = delay_by(:SECONDS, 1)
81
+ end
82
+ end
83
+ client.register(expectation)
84
+ ```
85
+
86
+ ### Clear & Reset Server
87
+
88
+ ##### Java
89
+
90
+ ```java
91
+ mockServerClient.clear(
92
+ request()
93
+ .withMethod("POST")
94
+ .withPath("/login")
95
+ );
96
+ mockServerClient.reset();
97
+ ```
98
+
99
+ ##### Ruby
100
+
101
+ ```ruby
102
+ client.clear(request('POST', '/login'))
103
+ client.reset
104
+ ```
105
+ ### Verifying Behavior
106
+
107
+ ##### Java
108
+
109
+ ```java
110
+ new ProxyClient("localhost", 9090).verify(
111
+ request()
112
+ .withMethod("POST")
113
+ .withPath("/login")
114
+ .withBody(exact("{username: 'foo', password: 'bar'}"))
115
+ .withCookies(
116
+ new Cookie("sessionId", ".*")
117
+ ),
118
+ Times.exactly(1)
119
+ );
120
+ ```
121
+
122
+ ##### Ruby
123
+
124
+ ```ruby
125
+
126
+ # Not providing times here because the default is exactly(1) i.e. the second argument to verify method
127
+ ProxyClient.new('localhost', 9090).verify(request(:POST, '/login') do |request|
128
+ request.body = exact("{username: 'foo', password: 'bar'}")
129
+ request.cookies = [cookie("sessionId", ".*")]
130
+ end)
131
+ ```
132
+
133
+
134
+ ### Analyzing Behavior
135
+
136
+ ##### Java
137
+
138
+ ```java
139
+ new ProxyClient("localhost", 9090).dumpToLogAsJava(
140
+ request()
141
+ .withMethod("POST")
142
+ .withPath("/login")
143
+ );
144
+ ```
145
+
146
+ ##### Ruby
147
+ ```ruby
148
+ # Second argument is true to set output to Java; false to set output to default JSON (default)
149
+ ProxyClient.new('localhost', 9090).dump_log(request(:POST, '/login'), true)
150
+ ```
151
+
152
+ ## Complete Ruby DSL
153
+ The DSL is provided via the `MockServer::Model::DSL` module. Include this module in your code to make the DSL available.
154
+
155
+ Request
156
+ * **request**: Used to build a request object. If a block is passed, will configure request first and then return the configured request. Example: `request(:POST, '/login') {|r| r.headers << header("Content-Type", "application/json")}`.
157
+ * **http_request**: Alias for `request`.
158
+
159
+ Body
160
+ * **body**: Create a string body object (use in a response). Example: `body("unaccepted")`.
161
+ * **exact**: Create a body of type `EXACT`. Example: `exact('{"reason": "unauthorized"}')`.
162
+ * **regex**: Create a body of type `REGEX`. Example: `regex('username[a-z]{4}')`.
163
+ * **xpath**: Used to create a body of type `XPATH`. Example: `xpath("/element[key = 'some_key' and value = 'some_value']")`.
164
+ * **parameterized**; Create a body to type `PARAMETERS`. Example `parameterized(parameter('someValue', 1, 2), parameter('otherValue', 4, 5))`.
165
+
166
+ Parameters
167
+ * **parameter**: Create a generic parameter. Example: `parameter('key', 'value1' , 'value2')`.
168
+ * **cookie**: Create a cookie (same as `parameter` above but exists as syntactic sugar). Example: `cookie('sessionId', 'sessionid1ldj')`.
169
+ * **header**: Create a header (same as `parameter` above but exists as syntactic sugar). Example: `header('Content-Type', 'application/json')`.
170
+
171
+ Forward
172
+ * **forward**: Create a forwarding response. If a block is passed, will configure forward response first and then return the configured object. Example: `forward {|f| f.scheme = 'HTTPS' }`.
173
+ * **http_forward**: Alias for `forward`.
174
+
175
+ Response
176
+ * **response**: Create a response object. If a block is passed, will configure response first and then return the configured response. Example: `response {|r| r.status_code = 201 }`.
177
+ * **http_response**: Alias for `response`.
178
+
179
+ Delay
180
+ * **delay_by**. Create a delay object in a response. Example : `delay_by(:MICROSECONDS, 20)`.
181
+
182
+ Times (used in Expectation)
183
+ * **times**: Create an 'times' object. If a block is passed, will configure the object first before returning it. Example: `times {|t| t.unlimited = false }`.
184
+ * **unlimited**: Create an object with unlimited repeats. Example: `unlimited()`. (No parameters).
185
+ * **once**. Create an object that repeats only once. Example: `once()`. (No parameters).
186
+ * **exactly**. Create an object that repeats exactly the number of times specified. Example: `exactly(2)`.
187
+ * **at_least**: Create an object that repeats at least the given number of times. (Use in verify). Example: `at_least(2)`.
188
+
189
+ Expectation (use in register)
190
+ * **expectation**: Create an expectation object. If a block is passed, will configure the object first before returning it. Example: `expectation {|e| e.request {|r| r.path = "index.html} }`.
191
+ Getter methods for `request`, `response` and `forward` methods will optionally accept a block. If block is passed object is configured before it is returned. The attribute `times` has conventional getter and setter methods.
192
+
193
+ ## Contributing
194
+
195
+ 1. Fork it ( https://github.com/[my-github-username]/mockserver-client/fork )
196
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
197
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
198
+ 4. Push to the branch (`git push origin my-new-feature`)
199
+ 5. Create a new Pull Request
200
+ 6. **IMPORTANT**: Keep code coverage high. Preferably above 95%. This project uses SimpleCov for code coverage which reports percentage coverage.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ require 'bundler/gem_tasks'
3
+ require 'rubocop/rake_task'
4
+ require 'rspec/mocks/version'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new(:rubocop)
9
+
10
+ desc 'Main task for this project to ensure the project passes build'
11
+ task default: [:rubocop, :spec, :build]
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ require_relative './mockserver/version'
3
+ require_relative './mockserver/mock_server_client'
4
+ require_relative './mockserver/proxy_client'
@@ -0,0 +1,112 @@
1
+ # encoding: UTF-8
2
+ require 'rest-client'
3
+ require 'logging_factory'
4
+ require_relative './model/times'
5
+ require_relative './model/request'
6
+
7
+ # An abstract client for making requests supported by mock and proxy client.
8
+ # @author Nayyara Samuel(mailto: nayyara.samuel@opower.com)
9
+ #
10
+ module MockServer
11
+ HTTP_REQUEST = 'httpRequest'
12
+ HTTP_RESPONSE = 'httpResponse'
13
+ HTTP_FORWARD = 'httpForward'
14
+ HTTP_TIMES = 'times'
15
+
16
+ RESET_ENDPOINT = '/reset'
17
+ CLEAR_ENDPOINT = '/clear'
18
+ RETRIEVE_ENDPOINT = '/retrieve'
19
+ DUMP_LOG_ENDPOINT = '/dumpToLog'
20
+
21
+ # An abstract client for making requests supported by mock and proxy client.
22
+ class AbstractClient
23
+ include Model::DSL
24
+ include Model
25
+
26
+ attr_accessor :logger
27
+
28
+ def initialize(host, port)
29
+ fail 'Cannot instantiate AbstractClient class. You must subclass it.' if self.class == AbstractClient
30
+ fail 'Host/port must not be nil' unless host && port
31
+ @base = RestClient::Resource.new("http://#{host}:#{port}")
32
+ @logger = ::LoggingFactory::DEFAULT_FACTORY.log(self.class)
33
+ end
34
+
35
+ # Clear all expectations with the given request
36
+ # @param request [Request] the request to use to clear an expectation
37
+ # @return [Object] the response from the clear action
38
+ def clear(request)
39
+ request = prepare_hash(HTTP_REQUEST => Request.new(request))
40
+ url = CLEAR_ENDPOINT
41
+
42
+ logger.debug("Clearing expectation with request: #{request}")
43
+ logger.debug("URL: #{url}. Payload: #{request.to_hash}")
44
+
45
+ response = @base[url].put(request.to_json, content_type: :json)
46
+ logger.debug("Got clear response: #{response.code}")
47
+ parse_response(response)
48
+ end
49
+
50
+ # Reset the mock server clearing all expectations previously registered
51
+ # @return [Object] the response from the reset action
52
+ def reset
53
+ request = {}
54
+ url = RESET_ENDPOINT
55
+
56
+ logger.debug('Resetting mockserver')
57
+ logger.debug("URL: #{url}. Payload: #{request.to_hash}")
58
+
59
+ response = @base[url].put(request.to_json)
60
+ logger.debug("Got reset response: #{response.code}")
61
+ parse_response(response)
62
+ end
63
+
64
+ # Retrieve the list of requests that have been processed by the server
65
+ # @param request [Request] to filter requests
66
+ # @return [Object] the list of responses processed by the server
67
+ def retrieve(request = nil)
68
+ request = request ? prepare_hash(HTTP_REQUEST => Request.new(request)) : {}
69
+ url = RETRIEVE_ENDPOINT
70
+
71
+ logger.debug('Retrieving request list from mockserver')
72
+ logger.debug("URL: #{url}. Payload: #{request.to_hash}")
73
+
74
+ response = @base[RETRIEVE_ENDPOINT].put(request.to_json)
75
+ logger.debug("Got retrieve response: #{response.code}")
76
+ parse_response(response)
77
+ end
78
+
79
+ # Request to dump logs to file
80
+ # @param java [Boolean] true to dump as Java code; false to dump as JSON
81
+ # @return [Object] the list of responses processed by the server
82
+ def dump_log(request = nil, java = false)
83
+ type_params = java ? '?type=java' : ''
84
+ url = "#{DUMP_LOG_ENDPOINT}#{type_params}"
85
+ request = Request.new(request) || {}
86
+
87
+ logger.debug('Sending dump log request to mockserver')
88
+ logger.debug("URL: #{url}. Payload: #{request.to_hash}")
89
+
90
+ response = @base[url].put(request.to_json)
91
+ logger.debug("Got dump to log response: #{response.code}")
92
+ parse_response(response)
93
+ end
94
+
95
+ # Verify that the given request is called the number of times expected
96
+ # @param request [Request] to filter requests
97
+ # @param times [Times] expected number of times
98
+ # @return [Object] the list of responses processed by the server that match the request
99
+ def verify(request, times = exactly(1))
100
+ logger.debug('Sending query for verify to mockserver')
101
+ results = retrieve(request)
102
+
103
+ # Reusing the times model here so interpreting values here
104
+ num_times = times.remaining_times
105
+ is_exact = !times.unlimited
106
+
107
+ fulfilled = is_exact ? (num_times == results.size) : (num_times <= results.size)
108
+ fail "Expected request to be present: [#{num_times}] (#{is_exact ? 'exactly' : 'at least'}). But found: [#{results.size}]" unless fulfilled
109
+ results
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,50 @@
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
+ url = EXPECTATION_ENDPOINT
24
+ request = create_expectation_request(expectation)
25
+
26
+ logger.debug('Registering new expectation')
27
+ logger.debug("URL: #{url} Payload: #{request.to_hash}")
28
+
29
+ response = @base[url].put(request.to_json, content_type: :json)
30
+ logger.debug("Got register response: #{response.code}")
31
+ parse_response(response)
32
+ end
33
+
34
+ private
35
+
36
+ # Create an expecation request to send to the expectation endpoint of
37
+ # @param expectation [Expectation] the expectation to create the request from
38
+ # @return [Hash] a hash representing the request to use in registering an expectation with the mock server
39
+ def create_expectation_request(expectation)
40
+ expectation_request = prepare_hash(HTTP_REQUEST => expectation.request,
41
+ HTTP_RESPONSE => expectation.response,
42
+ HTTP_FORWARD => expectation.forward,
43
+ HTTP_TIMES => expectation.times)
44
+
45
+ logger.debug("Expectation JSON: #{expectation_request.to_json}")
46
+ fail "You can only set either of #{[HTTP_RESPONSE, HTTP_FORWARD]}. But not both" if expectation_request[HTTP_RESPONSE] && expectation_request[HTTP_FORWARD]
47
+ expectation_request
48
+ end
49
+ end
50
+ end