mockserver-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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