flipp-mockserver-client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,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
|
+
"queryStringParameters": [
|
6
|
+
{
|
7
|
+
"name": "returnUrl",
|
8
|
+
"values": ["/account"]
|
9
|
+
}
|
10
|
+
],
|
11
|
+
"cookies": [
|
12
|
+
{
|
13
|
+
"name": "sessionId",
|
14
|
+
"value": "2By8LOhBmaW5nZXJwcmludCIlMDAzMW"
|
15
|
+
}
|
16
|
+
],
|
17
|
+
"body": {
|
18
|
+
"type": "STRING",
|
19
|
+
"value": "{username:'foo', password:'bar'}"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"httpRequest": {
|
3
|
+
"method": "POST",
|
4
|
+
"path": "/login",
|
5
|
+
"queryStringParameters": [
|
6
|
+
{
|
7
|
+
"values": [
|
8
|
+
"/account"
|
9
|
+
],
|
10
|
+
"name": "returnUrl"
|
11
|
+
}
|
12
|
+
],
|
13
|
+
"cookies": [
|
14
|
+
{
|
15
|
+
"name": "sessionId",
|
16
|
+
"value": "2By8LOhBmaW5nZXJwcmludCIlMDAzMW"
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"body": {
|
20
|
+
"type": "STRING",
|
21
|
+
"value": "{\"username\":\"foo\",\"password\":\"bar\"}"
|
22
|
+
}
|
23
|
+
},
|
24
|
+
"httpResponse": {
|
25
|
+
"statusCode": 401,
|
26
|
+
"headers": [
|
27
|
+
{
|
28
|
+
"values": [
|
29
|
+
"application/json; charset=utf-8"
|
30
|
+
],
|
31
|
+
"name": "Content-Type"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"values": [
|
35
|
+
"public, max-age=86400"
|
36
|
+
],
|
37
|
+
"name": "Cache-Control"
|
38
|
+
}
|
39
|
+
],
|
40
|
+
"body": "{\"message\":\"incorrect username and password combination\"}",
|
41
|
+
"delay": {
|
42
|
+
"timeUnit": "SECONDS",
|
43
|
+
"value": 1
|
44
|
+
}
|
45
|
+
},
|
46
|
+
"times": {
|
47
|
+
"remainingTimes": 2,
|
48
|
+
"unlimited": "false"
|
49
|
+
}
|
50
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"method": "POST",
|
3
|
+
"path": "/login",
|
4
|
+
"queryStringParameters": [
|
5
|
+
{
|
6
|
+
"values": [
|
7
|
+
"/account"
|
8
|
+
],
|
9
|
+
"name": "returnUrl"
|
10
|
+
}
|
11
|
+
],
|
12
|
+
"cookies": [
|
13
|
+
{
|
14
|
+
"value": "2By8LOhBmaW5nZXJwcmludCIlMDAzMW",
|
15
|
+
"name": "sessionId"
|
16
|
+
}
|
17
|
+
],
|
18
|
+
"body": {
|
19
|
+
"type": "STRING",
|
20
|
+
"value": "{\"username\":\"foo\",\"password\":\"bar\"}"
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rspec'
|
3
|
+
require 'net/http'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
require_relative '../../lib/mockserver-client'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
include WebMock::API
|
9
|
+
include MockServer
|
10
|
+
include MockServer::UtilityMethods
|
11
|
+
include MockServer::Model::DSL
|
12
|
+
|
13
|
+
# Only accept expect syntax do not allow old should syntax
|
14
|
+
config.expect_with :rspec do |c|
|
15
|
+
c.syntax = :expect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe MockServer::MockServerClient do
|
20
|
+
|
21
|
+
let(:client) { MockServer::MockServerClient.new('localhost', 8098) }
|
22
|
+
|
23
|
+
before do
|
24
|
+
# To suppress logging output to standard output, write to a temporary file
|
25
|
+
client.logger = LoggingFactory::DEFAULT_FACTORY.log('test', output: 'tmp.log', truncate: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_agent
|
29
|
+
uri = URI('http://api.nsa.gov:1337/agent')
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
31
|
+
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
32
|
+
req.body = { name: 'John Doe', role: 'agent' }.to_json
|
33
|
+
res = http.request(req)
|
34
|
+
puts "response #{res.body}"
|
35
|
+
rescue => e
|
36
|
+
puts "failed #{e}"
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'setup complex expectation' do
|
40
|
+
WebMock.allow_net_connect!
|
41
|
+
|
42
|
+
# given
|
43
|
+
mock_expectation = expectation do |expectation|
|
44
|
+
expectation.request do |request|
|
45
|
+
request.method = 'POST'
|
46
|
+
request.path = '/somePath'
|
47
|
+
request.query_string_parameters << parameter('param', 'someQueryStringValue')
|
48
|
+
request.headers << header('Header', 'someHeaderValue')
|
49
|
+
request.cookies << cookie('cookie', 'someCookieValue')
|
50
|
+
request.body = exact('someBody')
|
51
|
+
end
|
52
|
+
|
53
|
+
expectation.response do |response|
|
54
|
+
response.status_code = 201
|
55
|
+
response.headers << header('header', 'someHeaderValue')
|
56
|
+
response.body = body('someBody')
|
57
|
+
response.delay = delay_by(:SECONDS, 1)
|
58
|
+
end
|
59
|
+
|
60
|
+
expectation.times = exactly(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
# and
|
64
|
+
expect(client.register(mock_expectation).code).to eq(201)
|
65
|
+
|
66
|
+
# when
|
67
|
+
uri = URI('http://localhost:8098/somePath')
|
68
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
69
|
+
req = Net::HTTP::Post.new('/somePath?param=someQueryStringValue')
|
70
|
+
req['Header'] = 'someHeaderValue'
|
71
|
+
req['Cookie'] = 'cookie=someCookieValue'
|
72
|
+
req.body = 'someBody'
|
73
|
+
res = http.request(req)
|
74
|
+
|
75
|
+
# then
|
76
|
+
expect(res.code).to eq('201')
|
77
|
+
expect(res.body).to eq('someBody')
|
78
|
+
|
79
|
+
WebMock.disable_net_connect!
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,136 @@
|
|
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_string_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_string_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 expectation correctly from file' do
|
73
|
+
payload = FIXTURES.read('register_expectation.json')
|
74
|
+
mock_expectation = expectation_from_json(payload)
|
75
|
+
|
76
|
+
expect(to_camelized_hash(mock_expectation.to_hash)).to eq(payload)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'generates body correctly' do
|
80
|
+
expect(to_camelized_hash(regex('*/login'))).to eq('type' => 'REGEX', 'value' => '*/login')
|
81
|
+
expect(to_camelized_hash(xpath('/login[1]'))).to eq('type' => 'XPATH', 'value' => '/login[1]')
|
82
|
+
expect(to_camelized_hash(parameterized(parameter('token', '4jy5hh')))).to eq('type' => 'PARAMETERS', 'parameters' => [{ 'name' => 'token', 'values' => ['4jy5hh'] }])
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'generates times object correctly' do
|
86
|
+
expect(to_camelized_hash(unlimited)).to eq('unlimited' => 'true', 'remainingTimes' => 0)
|
87
|
+
expect(to_camelized_hash(at_least(2))).to eq('unlimited' => 'true', 'remainingTimes' => 2)
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
describe 'transforming request body' do
|
92
|
+
let(:expectation) { MockServer::Model::Expectation.new }
|
93
|
+
let(:request) { MockServer::Model::Request.new }
|
94
|
+
|
95
|
+
[{type: :XPATH, value: '/mock/instance'},
|
96
|
+
{type: :REGEX, value: '\d+.\d+'},
|
97
|
+
{type: :STRING, value: 'Mockserver is great'}].each do |request_hash|
|
98
|
+
|
99
|
+
it "generates a request body of type #{request_hash[:type]}" do
|
100
|
+
request.body = MockServer::Model::Body.new(request_hash)
|
101
|
+
expectation.request = request
|
102
|
+
|
103
|
+
expect(to_camelized_hash(expectation)).to eq({
|
104
|
+
"httpRequest" => {
|
105
|
+
"method" => "GET",
|
106
|
+
"body" => {
|
107
|
+
"type" => request_hash[:type].to_s,
|
108
|
+
"value" => request_hash[:value].to_s
|
109
|
+
}
|
110
|
+
}
|
111
|
+
})
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when request body type binary' do
|
116
|
+
let(:body) do
|
117
|
+
MockServer::Model::Body.new(type: :BINARY, value: 'TW9ja3NlcnZlciBpcyBncmVhdAo=')
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'correctly transforms to a string and updates the object' do
|
121
|
+
request.body = body
|
122
|
+
expectation.request = request
|
123
|
+
|
124
|
+
expect(to_camelized_hash(expectation)).to eq({
|
125
|
+
"httpRequest" => {
|
126
|
+
"method" => "GET",
|
127
|
+
"body" => {
|
128
|
+
"type" => "STRING",
|
129
|
+
"value" => "Mockserver is great\n"
|
130
|
+
}
|
131
|
+
}
|
132
|
+
})
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,80 @@
|
|
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, headers: { 'Content-Type' => 'application/json' }).to_return(status: 201)
|
16
|
+
stub_request(:put, /.+\/clear/).with(body: search_request_json, headers: { 'Content-Type' => 'application/json' }).to_return(status: 202)
|
17
|
+
stub_request(:put, /.+\/reset/).with(headers: { 'Content-Type' => 'application/json' }).to_return(status: 202)
|
18
|
+
stub_request(:put, /.+\/retrieve/).with(body: search_request_json, headers: { 'Content-Type' => 'application/json' }).to_return(
|
19
|
+
body: '[]',
|
20
|
+
status: 200
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'registers an expectation correctly' do
|
25
|
+
mock_expectation = expectation do |expectation|
|
26
|
+
expectation.request do |request|
|
27
|
+
request.method = 'POST'
|
28
|
+
request.path = '/login'
|
29
|
+
request.query_string_parameters << parameter('returnUrl', '/account')
|
30
|
+
request.cookies << cookie('sessionId', '2By8LOhBmaW5nZXJwcmludCIlMDAzMW')
|
31
|
+
request.body = exact({ username: 'foo', password: 'bar' }.to_json)
|
32
|
+
end
|
33
|
+
|
34
|
+
expectation.response do |response|
|
35
|
+
response.status_code = 401
|
36
|
+
response.headers << header('Content-Type', 'application/json; charset=utf-8')
|
37
|
+
response.headers << header('Cache-Control', 'public, max-age=86400')
|
38
|
+
response.body = body({ message: 'incorrect username and password combination' }.to_json)
|
39
|
+
response.delay = delay_by(:SECONDS, 1)
|
40
|
+
end
|
41
|
+
|
42
|
+
expectation.times = exactly(2)
|
43
|
+
end
|
44
|
+
|
45
|
+
response = client.register(mock_expectation)
|
46
|
+
expect(response.code).to eq(201)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises an error when trying to set both forward and response' do
|
50
|
+
mock_expectation = expectation do |expectation|
|
51
|
+
expectation.request do |request|
|
52
|
+
request.method = 'POST'
|
53
|
+
request.path = '/login'
|
54
|
+
end
|
55
|
+
|
56
|
+
expectation.response do |response|
|
57
|
+
response.status_code = 401
|
58
|
+
end
|
59
|
+
|
60
|
+
expectation.forward do |forward|
|
61
|
+
forward.scheme = :HTTP
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
expect { client.register(mock_expectation) }.to raise_error(RuntimeError, 'You can only set either of ["httpResponse", "httpForward"]. But not both')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'clears requests from the mock server' do
|
69
|
+
expect(client.clear(request(:POST, '/login')).code).to eq(202)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'resets the mock server' do
|
73
|
+
expect(client.reset.code).to eq(202)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'retrieves requests correctly' do
|
77
|
+
expect(client.retrieve(request(:POST, '/login')).code).to eq(200)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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(:retrieved_request) { FIXTURES.read('retrieved_request.json') }
|
8
|
+
let(:retrieved_request_json) { retrieved_request.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: "[#{retrieved_request_json}, #{retrieved_request_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
|
+
response = response.map { |mock| to_camelized_hash(mock.to_hash) }
|
27
|
+
expect(response).to eq([retrieved_request, retrieved_request])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises an error when verification fails' do
|
31
|
+
expect { client.verify(request(:POST, '/login')) }.to raise_error(RuntimeError, 'Expected request to be present: [1] (exactly). But found: [2]')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'dumps to logs correctly do' do
|
35
|
+
expect(client.dump_log.code).to eq(202)
|
36
|
+
expect(client.dump_log({}, true).code).to eq(202)
|
37
|
+
end
|
38
|
+
end
|