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