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 +15 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/README.md +200 -0
- data/Rakefile +11 -0
- data/lib/mockserver-client.rb +4 -0
- data/lib/mockserver/abstract_client.rb +112 -0
- data/lib/mockserver/mock_server_client.rb +50 -0
- data/lib/mockserver/model/array_of.rb +85 -0
- data/lib/mockserver/model/body.rb +56 -0
- data/lib/mockserver/model/delay.rb +34 -0
- data/lib/mockserver/model/enum.rb +47 -0
- data/lib/mockserver/model/expectation.rb +60 -0
- data/lib/mockserver/model/forward.rb +41 -0
- data/lib/mockserver/model/parameter.rb +46 -0
- data/lib/mockserver/model/request.rb +52 -0
- data/lib/mockserver/model/response.rb +39 -0
- data/lib/mockserver/model/times.rb +53 -0
- data/lib/mockserver/proxy_client.rb +9 -0
- data/lib/mockserver/utility_methods.rb +44 -0
- data/lib/mockserver/version.rb +5 -0
- data/mockserver-client.gemspec +31 -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 +52 -0
- data/spec/fixtures/search_request.json +6 -0
- data/spec/fixtures/times_once.json +6 -0
- data/spec/mockserver/builder_spec.rb +83 -0
- data/spec/mockserver/mock_client_spec.rb +77 -0
- data/spec/mockserver/proxy_client_spec.rb +37 -0
- data/spec/spec_helper.rb +61 -0
- metadata +240 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'hashie'
|
3
|
+
require_relative './enum'
|
4
|
+
|
5
|
+
#
|
6
|
+
# A class to model the number of times an expectation should be respected.
|
7
|
+
# @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
|
8
|
+
#
|
9
|
+
module MockServer::Model
|
10
|
+
# Enum for boolean values since Ruby does not have this by default
|
11
|
+
class Boolean < Enum
|
12
|
+
def allowed_values
|
13
|
+
[true, false]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Model for times class
|
18
|
+
class Times < Hashie::Dash
|
19
|
+
include Hashie::Extensions::MethodAccess
|
20
|
+
include Hashie::Extensions::IgnoreUndeclared
|
21
|
+
include Hashie::Extensions::Coercion
|
22
|
+
|
23
|
+
property :remaining_times, default: 0
|
24
|
+
property :unlimited, default: false
|
25
|
+
|
26
|
+
coerce_key :unlimited, Boolean
|
27
|
+
end
|
28
|
+
|
29
|
+
# DSL methods related to times
|
30
|
+
module DSL
|
31
|
+
def unlimited
|
32
|
+
Times.new(unlimited: true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def once
|
36
|
+
Times.new(remaining_times: 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
def exactly(num)
|
40
|
+
Times.new(remaining_times: num)
|
41
|
+
end
|
42
|
+
|
43
|
+
def at_least(num)
|
44
|
+
Times.new(remaining_times: num, unlimited: true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def times(&_)
|
48
|
+
obj = once
|
49
|
+
yield obj if block_given?
|
50
|
+
obj
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
#
|
6
|
+
# A module that has common utility methods used by this project
|
7
|
+
# @author:: Nayyara Samuel (mailto: nayyara.samuel@opower.com)
|
8
|
+
#
|
9
|
+
module MockServer::UtilityMethods
|
10
|
+
# Does the following filter/transform operations on a hash
|
11
|
+
# - exclude null or empty valued keys from the hash
|
12
|
+
# - camelize the keys of the hash
|
13
|
+
# @param hash [Object] an object which will be used to create the hash. Must support :to_hash method
|
14
|
+
# @return [Hash] the transformed hash
|
15
|
+
def prepare_hash(obj)
|
16
|
+
obj = obj && obj.respond_to?(:to_hash) ? obj.to_hash : obj
|
17
|
+
|
18
|
+
if obj.is_a?(Hash)
|
19
|
+
obj.each_with_object({}) do |(k, v), acc|
|
20
|
+
is_empty = v.nil? || (v.respond_to?(:empty?) ? v.empty? : false)
|
21
|
+
acc[camelize(k)] = prepare_hash(v) unless is_empty
|
22
|
+
end
|
23
|
+
elsif obj.respond_to?(:map)
|
24
|
+
obj.map { |element| prepare_hash(element) }
|
25
|
+
else
|
26
|
+
obj
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Object] an object to camelize the string representation of
|
31
|
+
# @return [String] the string converted to camelcase with first letter in lower case
|
32
|
+
def camelize(str)
|
33
|
+
str.to_s.camelize(:lower)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Parse string response into JSON
|
37
|
+
# @param response [Response] from RestClient response
|
38
|
+
# @return [Hash] the parsed response or the object unmodified if parsing is not possible
|
39
|
+
def parse_response(response)
|
40
|
+
JSON.parse(response)
|
41
|
+
rescue JSON::ParserError
|
42
|
+
response
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mockserver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'mockserver-client'
|
8
|
+
spec.version = MockServer::VERSION
|
9
|
+
spec.authors = ['Nayyara Samuel']
|
10
|
+
spec.email = ['nayyara.samuel@gmail.com']
|
11
|
+
spec.summary = %q(A Ruby client for MockServer)
|
12
|
+
spec.required_ruby_version = '~> 1.9.3'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
20
|
+
spec.add_development_dependency 'rake', '~> 10.3.2'
|
21
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
22
|
+
spec.add_development_dependency 'simplecov', '~> 0.8.2'
|
23
|
+
spec.add_development_dependency 'webmock', '~> 1.18'
|
24
|
+
spec.add_development_dependency 'rubocop', '~> 0.23.0'
|
25
|
+
|
26
|
+
spec.add_dependency 'hashie', '~> 3.0'
|
27
|
+
spec.add_dependency 'json', '~> 1.8.1'
|
28
|
+
spec.add_dependency 'activesupport', '~> 4.1.1'
|
29
|
+
spec.add_dependency 'rest-client', '~> 1.6.7'
|
30
|
+
spec.add_dependency 'logging_factory', '~> 0.0.2'
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"httpResponse": {
|
3
|
+
"statusCode": 401,
|
4
|
+
"headers": [
|
5
|
+
{
|
6
|
+
"name": "Content-Type",
|
7
|
+
"values": ["application/json; charset=utf-8"]
|
8
|
+
},
|
9
|
+
{
|
10
|
+
"name": "Cache-Control",
|
11
|
+
"values": ["public, max-age=86400"]
|
12
|
+
}
|
13
|
+
],
|
14
|
+
"body": "incorrect username and password combination",
|
15
|
+
"delay": {
|
16
|
+
"timeUnit": "SECONDS",
|
17
|
+
"value": 1
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"httpRequest": {
|
3
|
+
"method": "POST",
|
4
|
+
"path": "/login",
|
5
|
+
"queryParameters": [
|
6
|
+
{
|
7
|
+
"name": "returnUrl",
|
8
|
+
"values": ["/account"]
|
9
|
+
}
|
10
|
+
],
|
11
|
+
"cookies": [
|
12
|
+
{
|
13
|
+
"name": "sessionId",
|
14
|
+
"values": ["2By8LOhBmaW5nZXJwcmludCIlMDAzMW"]
|
15
|
+
}
|
16
|
+
],
|
17
|
+
"body": {
|
18
|
+
"type": "EXACT",
|
19
|
+
"value": "{username:'foo', password:'bar'}"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"httpRequest": {
|
3
|
+
"method": "POST",
|
4
|
+
"path": "/login",
|
5
|
+
"queryParameters": [
|
6
|
+
{
|
7
|
+
"values": [
|
8
|
+
"/account"
|
9
|
+
],
|
10
|
+
"name": "returnUrl"
|
11
|
+
}
|
12
|
+
],
|
13
|
+
"cookies": [
|
14
|
+
{
|
15
|
+
"values": [
|
16
|
+
"2By8LOhBmaW5nZXJwcmludCIlMDAzMW"
|
17
|
+
],
|
18
|
+
"name": "sessionId"
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"body": {
|
22
|
+
"type": "EXACT",
|
23
|
+
"value": "{\"username\":\"foo\",\"password\":\"bar\"}"
|
24
|
+
}
|
25
|
+
},
|
26
|
+
"httpResponse": {
|
27
|
+
"statusCode": 401,
|
28
|
+
"headers": [
|
29
|
+
{
|
30
|
+
"values": [
|
31
|
+
"application/json; charset=utf-8"
|
32
|
+
],
|
33
|
+
"name": "Content-Type"
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"values": [
|
37
|
+
"public, max-age=86400"
|
38
|
+
],
|
39
|
+
"name": "Cache-Control"
|
40
|
+
}
|
41
|
+
],
|
42
|
+
"body": "{\"message\":\"incorrect username and password combination\"}",
|
43
|
+
"delay": {
|
44
|
+
"timeUnit": "SECONDS",
|
45
|
+
"value": 1
|
46
|
+
}
|
47
|
+
},
|
48
|
+
"times": {
|
49
|
+
"remainingTimes": 2,
|
50
|
+
"unlimited": false
|
51
|
+
}
|
52
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative '../spec_helper'
|
3
|
+
|
4
|
+
describe MockServer::Model::DSL do
|
5
|
+
|
6
|
+
it 'generates http requests correctly' do
|
7
|
+
mock_request = http_request(:POST, '/login')
|
8
|
+
mock_request.query_parameters = [parameter('returnUrl', '/account')]
|
9
|
+
mock_request.cookies = [cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')]
|
10
|
+
mock_request.body = exact("{username:'foo', password:'bar'}")
|
11
|
+
|
12
|
+
expect(to_camelized_hash(HTTP_REQUEST => mock_request)).to eq(FIXTURES.read('post_login_request.json'))
|
13
|
+
|
14
|
+
# Block style
|
15
|
+
mock_request = request(:POST, '/login') do |request|
|
16
|
+
request.query_parameters = [parameter('returnUrl', '/account')]
|
17
|
+
request.cookies = [cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')]
|
18
|
+
request.body = exact("{username:'foo', password:'bar'}")
|
19
|
+
end
|
20
|
+
|
21
|
+
expect(to_camelized_hash(HTTP_REQUEST => mock_request)).to eq(FIXTURES.read('post_login_request.json'))
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'generates http responses correctly' do
|
25
|
+
mock_response = http_response
|
26
|
+
mock_response.status_code = 401
|
27
|
+
mock_response.headers = [header('Content-Type', 'application/json; charset=utf-8')]
|
28
|
+
mock_response.headers << header('Cache-Control', 'public, max-age=86400')
|
29
|
+
mock_response.body = body('incorrect username and password combination')
|
30
|
+
mock_response.delay = delay_by(:SECONDS, 1)
|
31
|
+
|
32
|
+
expect(to_camelized_hash(HTTP_RESPONSE => mock_response)).to eq(FIXTURES.read('incorrect_login_response.json'))
|
33
|
+
|
34
|
+
# Block style
|
35
|
+
mock_response = response do |response|
|
36
|
+
response.status_code = 401
|
37
|
+
response.headers = [header('Content-Type', 'application/json; charset=utf-8')]
|
38
|
+
response.headers << header('Cache-Control', 'public, max-age=86400')
|
39
|
+
response.body = body('incorrect username and password combination')
|
40
|
+
response.delay = delay_by(:SECONDS, 1)
|
41
|
+
end
|
42
|
+
|
43
|
+
expect(to_camelized_hash(HTTP_RESPONSE => mock_response)).to eq(FIXTURES.read('incorrect_login_response.json'))
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'generates http forwards correctly' do
|
47
|
+
mock_forward = http_forward
|
48
|
+
mock_forward.host = 'www.mock-server.com'
|
49
|
+
mock_forward.port = 80
|
50
|
+
|
51
|
+
expect(to_camelized_hash(HTTP_FORWARD => mock_forward)).to eq(FIXTURES.read('forward_mockserver.json'))
|
52
|
+
|
53
|
+
# Block style
|
54
|
+
mock_forward = forward do |forward|
|
55
|
+
forward.host = 'www.mock-server.com'
|
56
|
+
forward.port = 80
|
57
|
+
end
|
58
|
+
|
59
|
+
expect(to_camelized_hash(HTTP_FORWARD => mock_forward)).to eq(FIXTURES.read('forward_mockserver.json'))
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'generates times correctly' do
|
63
|
+
expect(to_camelized_hash(HTTP_TIMES => exactly(1))).to eq(FIXTURES.read('times_once.json'))
|
64
|
+
|
65
|
+
# Block style
|
66
|
+
mock_times = times do |times|
|
67
|
+
times.remaining_times = 1
|
68
|
+
end
|
69
|
+
expect(to_camelized_hash(HTTP_TIMES => mock_times)).to eq(FIXTURES.read('times_once.json'))
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'generates body correctly' do
|
73
|
+
expect(to_camelized_hash(regex('*/login'))).to eq('type' => 'REGEX', 'value' => '*/login')
|
74
|
+
expect(to_camelized_hash(xpath('/login[1]'))).to eq('type' => 'XPATH', 'value' => '/login[1]')
|
75
|
+
expect(to_camelized_hash(parameterized(parameter('token', '4jy5hh')))).to eq('type' => 'PARAMETERS', 'parameters' => [{ 'name' => 'token', 'values' => ['4jy5hh'] }])
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'generates times object correctly' do
|
79
|
+
expect(to_camelized_hash(unlimited)).to eq('unlimited' => 'true', 'remainingTimes' => 0)
|
80
|
+
expect(to_camelized_hash(at_least(2))).to eq('unlimited' => 'true', 'remainingTimes' => 2)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative '../spec_helper'
|
3
|
+
|
4
|
+
describe MockServer::MockServerClient do
|
5
|
+
|
6
|
+
let(:client) { MockServer::MockServerClient.new('localhost', 8080) }
|
7
|
+
let(:register_expectation_json) { FIXTURES.read('register_expectation.json').to_json }
|
8
|
+
let(:search_request_json) { FIXTURES.read('search_request.json').to_json }
|
9
|
+
|
10
|
+
before do
|
11
|
+
# To suppress logging output to standard output, write to a temporary file
|
12
|
+
client.logger = LoggingFactory::DEFAULT_FACTORY.log('test', output: 'tmp.log', truncate: true)
|
13
|
+
|
14
|
+
# Stub requests
|
15
|
+
stub_request(:put, /.+\/expectation/).with(body: register_expectation_json).to_return(status: 201)
|
16
|
+
stub_request(:put, /.+\/clear/).with(body: search_request_json).to_return(status: 202)
|
17
|
+
stub_request(:put, /.+\/reset/).to_return(status: 202)
|
18
|
+
stub_request(:put, /.+\/retrieve/).with(body: search_request_json).to_return(body: '', status: 200)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'registers an expectation correctly' do
|
22
|
+
mock_expectation = expectation do |expectation|
|
23
|
+
expectation.request do |request|
|
24
|
+
request.method = 'POST'
|
25
|
+
request.path = '/login'
|
26
|
+
request.query_parameters << parameter('returnUrl', '/account')
|
27
|
+
request.cookies << cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')
|
28
|
+
request.body = exact({ username: 'foo', password: 'bar' }.to_json)
|
29
|
+
end
|
30
|
+
|
31
|
+
expectation.response do |response|
|
32
|
+
response.status_code = 401
|
33
|
+
response.headers << header('Content-Type', 'application/json; charset=utf-8')
|
34
|
+
response.headers << header('Cache-Control', 'public, max-age=86400')
|
35
|
+
response.body = body({ message: 'incorrect username and password combination' }.to_json)
|
36
|
+
response.delay = delay_by(:SECONDS, 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
expectation.times = exactly(2)
|
40
|
+
end
|
41
|
+
|
42
|
+
response = client.register(mock_expectation)
|
43
|
+
expect(response.code).to eq(201)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises an error when trying to set both forward and response' do
|
47
|
+
mock_expectation = expectation do |expectation|
|
48
|
+
expectation.request do |request|
|
49
|
+
request.method = 'POST'
|
50
|
+
request.path = '/login'
|
51
|
+
end
|
52
|
+
|
53
|
+
expectation.response do |response|
|
54
|
+
response.status_code = 401
|
55
|
+
end
|
56
|
+
|
57
|
+
expectation.forward do |forward|
|
58
|
+
forward.scheme = :HTTP
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
expect { client.register(mock_expectation) }.to raise_error(RuntimeError, 'You can only set either of ["httpResponse", "httpForward"]. But not both')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'clears requests from the mock server' do
|
66
|
+
expect(client.clear(request(:POST, '/login')).code).to eq(202)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'resets the mock server' do
|
70
|
+
expect(client.reset.code).to eq(202)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'retrieves requests correctly' do
|
74
|
+
expect(client.retrieve(request(:POST, '/login')).code).to eq(200)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative '../spec_helper'
|
3
|
+
|
4
|
+
describe MockServer::ProxyClient do
|
5
|
+
|
6
|
+
let(:client) { MockServer::ProxyClient.new('localhost', 8080) }
|
7
|
+
let(:register_expectation) { FIXTURES.read('register_expectation.json') }
|
8
|
+
let(:register_expectation_json) { register_expectation.to_json }
|
9
|
+
let(:search_request_json) { FIXTURES.read('search_request.json').to_json }
|
10
|
+
|
11
|
+
before do
|
12
|
+
# To suppress logging output to standard output, write to a temporary file
|
13
|
+
client.logger = LoggingFactory::DEFAULT_FACTORY.log('test', output: 'tmp.log', truncate: true)
|
14
|
+
|
15
|
+
# Stub requests
|
16
|
+
stub_request(:put, /.+\/retrieve/).with(body: search_request_json).to_return(
|
17
|
+
body: "[#{register_expectation_json}, #{register_expectation_json}]",
|
18
|
+
status: 200
|
19
|
+
)
|
20
|
+
stub_request(:put, /.+\/dumpToLog$/).to_return(status: 202).once
|
21
|
+
stub_request(:put, /.+\/dumpToLog\?type=java$/).to_return(status: 202).once
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'verifies requests correctly' do
|
25
|
+
response = client.verify(request(:POST, '/login'), exactly(2))
|
26
|
+
expect(response).to eq([register_expectation, register_expectation])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an error when verification fails' do
|
30
|
+
expect { client.verify(request(:POST, '/login')) }.to raise_error(RuntimeError, 'Expected request to be present: [1] (exactly). But found: [2]')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'dumps to logs correctly do' do
|
34
|
+
expect(client.dump_log.code).to eq(202)
|
35
|
+
expect(client.dump_log({}, true).code).to eq(202)
|
36
|
+
end
|
37
|
+
end
|