pacto 0.2.5 → 0.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +0 -2
- data/.rubocop-todo.yml +51 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +4 -2
- data/Guardfile +28 -14
- data/README.md +81 -51
- data/Rakefile +24 -11
- data/features/generation/generation.feature +25 -0
- data/features/journeys/validation.feature +74 -0
- data/features/support/env.rb +16 -0
- data/lib/pacto.rb +63 -34
- data/lib/pacto/contract.rb +25 -11
- data/lib/pacto/contract_factory.rb +13 -44
- data/lib/pacto/core/callback.rb +11 -0
- data/lib/pacto/core/configuration.rb +34 -0
- data/lib/pacto/core/contract_repository.rb +44 -0
- data/lib/pacto/erb_processor.rb +18 -0
- data/lib/pacto/exceptions/invalid_contract.rb +10 -1
- data/lib/pacto/extensions.rb +2 -2
- data/lib/pacto/generator.rb +75 -0
- data/lib/pacto/hash_merge_processor.rb +14 -0
- data/lib/pacto/hooks/erb_hook.rb +17 -0
- data/lib/pacto/logger.rb +42 -0
- data/lib/pacto/meta_schema.rb +17 -0
- data/lib/pacto/rake_task.rb +75 -12
- data/lib/pacto/request.rb +3 -4
- data/lib/pacto/response.rb +27 -19
- data/lib/pacto/server.rb +2 -0
- data/lib/pacto/server/dummy.rb +45 -0
- data/lib/pacto/server/playback_servlet.rb +21 -0
- data/lib/pacto/stubs/built_in.rb +57 -0
- data/lib/pacto/version.rb +1 -1
- data/pacto.gemspec +8 -2
- data/resources/contract_schema.json +216 -0
- data/spec/coveralls_helper.rb +10 -0
- data/spec/integration/data/strict_contract.json +33 -0
- data/spec/integration/data/templating_contract.json +25 -0
- data/spec/integration/e2e_spec.rb +40 -7
- data/spec/integration/templating_spec.rb +55 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/unit/data/simple_contract.json +22 -0
- data/spec/unit/hooks/erb_hook_spec.rb +51 -0
- data/spec/unit/pacto/configuration_spec.rb +51 -0
- data/spec/unit/pacto/contract_factory_spec.rb +4 -35
- data/spec/unit/pacto/contract_spec.rb +59 -31
- data/spec/unit/pacto/core/configuration_spec.rb +28 -0
- data/spec/unit/pacto/core/contract_repository_spec.rb +133 -0
- data/spec/unit/pacto/erb_processor_spec.rb +23 -0
- data/spec/unit/pacto/extensions_spec.rb +11 -11
- data/spec/unit/pacto/generator_spec.rb +142 -0
- data/spec/unit/pacto/hash_merge_processor_spec.rb +20 -0
- data/spec/unit/pacto/logger_spec.rb +44 -0
- data/spec/unit/pacto/meta_schema_spec.rb +70 -0
- data/spec/unit/pacto/pacto_spec.rb +32 -58
- data/spec/unit/pacto/request_spec.rb +83 -34
- data/spec/unit/pacto/response_adapter_spec.rb +9 -11
- data/spec/unit/pacto/response_spec.rb +68 -68
- data/spec/unit/pacto/server/playback_servlet_spec.rb +24 -0
- data/spec/unit/pacto/stubs/built_in_spec.rb +168 -0
- metadata +291 -147
- data/.rspec_integration +0 -4
- data/.rspec_unit +0 -4
- data/lib/pacto/file_pre_processor.rb +0 -12
- data/lib/pacto/instantiated_contract.rb +0 -62
- data/spec/integration/spec_helper.rb +0 -1
- data/spec/integration/utils/dummy_server.rb +0 -34
- data/spec/unit/pacto/file_pre_processor_spec.rb +0 -13
- data/spec/unit/pacto/instantiated_contract_spec.rb +0 -224
- data/spec/unit/spec_helper.rb +0 -5
@@ -1,12 +1,13 @@
|
|
1
1
|
module Pacto
|
2
2
|
describe Request do
|
3
|
-
let(:host)
|
4
|
-
let(:method)
|
5
|
-
let(:path)
|
6
|
-
let(:headers)
|
7
|
-
let(:params)
|
8
|
-
|
9
|
-
let(:
|
3
|
+
let(:host) { 'http://localhost' }
|
4
|
+
let(:method) { 'GET' }
|
5
|
+
let(:path) { '/hello_world' }
|
6
|
+
let(:headers) { {'accept' => 'application/json'} }
|
7
|
+
let(:params) { {'foo' => 'bar'} }
|
8
|
+
let(:params_as_json) { "{\"foo\":\"bar\"}" }
|
9
|
+
let(:absolute_uri) { "#{host}#{path}" }
|
10
|
+
subject(:request) do
|
10
11
|
described_class.new(host, {
|
11
12
|
'method' => method,
|
12
13
|
'path' => path,
|
@@ -14,58 +15,106 @@ module Pacto
|
|
14
15
|
'params' => params
|
15
16
|
})
|
16
17
|
end
|
17
|
-
subject { request }
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
it 'has a host' do
|
20
|
+
expect(request.host).to eq host
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#method' do
|
24
|
+
it 'delegates to definition' do
|
25
|
+
expect(request.method).to eq :get
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'downcases the method' do
|
29
|
+
expect(request.method).to eq request.method.downcase
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns a symbol' do
|
33
|
+
expect(request.method).to be_kind_of Symbol
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#path' do
|
38
|
+
it 'delegates to definition' do
|
39
|
+
expect(request.path).to eq path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#headers' do
|
44
|
+
it 'delegates to definition' do
|
45
|
+
expect(request.headers).to eq headers
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#params' do
|
50
|
+
it 'delegates to definition' do
|
51
|
+
expect(request.params).to eq params
|
52
|
+
end
|
53
|
+
end
|
23
54
|
|
24
55
|
describe '#execute' do
|
25
|
-
let(:connection)
|
26
|
-
let(:response)
|
27
|
-
let(:adapted_response) { double
|
56
|
+
let(:connection) { double 'connection' }
|
57
|
+
let(:response) { double 'response' }
|
58
|
+
let(:adapted_response) { double 'adapted response' }
|
59
|
+
|
60
|
+
before do
|
61
|
+
HTTParty.stub(:get => response)
|
62
|
+
HTTParty.stub(:post => response)
|
63
|
+
ResponseAdapter.stub(:new => adapted_response)
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'for any request' do
|
67
|
+
it 'processes the response with an ResponseAdapter' do
|
68
|
+
ResponseAdapter.should_receive(:new).
|
69
|
+
with(response).
|
70
|
+
and_return(adapted_response)
|
71
|
+
request.execute
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns the adapted response' do
|
75
|
+
expect(request.execute).to be adapted_response
|
76
|
+
end
|
77
|
+
end
|
28
78
|
|
29
79
|
context 'for a GET request' do
|
30
|
-
it '
|
80
|
+
it 'makes the request thru the http client' do
|
31
81
|
HTTParty.should_receive(:get).
|
32
|
-
with(
|
82
|
+
with(absolute_uri, {:query => params, :headers => headers}).
|
33
83
|
and_return(response)
|
34
|
-
|
35
|
-
request.execute.should == adapted_response
|
84
|
+
request.execute
|
36
85
|
end
|
37
86
|
end
|
38
87
|
|
39
88
|
context 'for a POST request' do
|
40
|
-
let(:method)
|
89
|
+
let(:method) { 'POST' }
|
41
90
|
|
42
|
-
it '
|
91
|
+
it 'makes the request thru the http client' do
|
43
92
|
HTTParty.should_receive(:post).
|
44
|
-
with(
|
93
|
+
with(absolute_uri, {:body => params_as_json, :headers => headers}).
|
45
94
|
and_return(response)
|
46
|
-
|
47
|
-
request.execute.should == adapted_response
|
95
|
+
request.execute
|
48
96
|
end
|
49
97
|
end
|
50
98
|
end
|
51
99
|
|
52
|
-
describe
|
53
|
-
it
|
54
|
-
request.absolute_uri.
|
100
|
+
describe '#absolute_uri' do
|
101
|
+
it 'returns the host followed by the path' do
|
102
|
+
expect(request.absolute_uri).to eq absolute_uri
|
55
103
|
end
|
56
104
|
end
|
57
105
|
|
58
|
-
describe
|
59
|
-
context
|
60
|
-
it
|
61
|
-
request.full_uri.
|
106
|
+
describe '#full_uri' do
|
107
|
+
context 'when the request has a query' do
|
108
|
+
it 'returns the host followed by the path and the query' do
|
109
|
+
expect(request.full_uri).to eq 'http://localhost/hello_world?foo=bar'
|
62
110
|
end
|
63
111
|
end
|
64
112
|
|
65
|
-
context
|
113
|
+
context 'when the query does not have a query' do
|
66
114
|
let(:params) { {} }
|
67
|
-
|
68
|
-
|
115
|
+
|
116
|
+
it 'returns the host followed by the path' do
|
117
|
+
expect(request.absolute_uri).to eq 'http://localhost/hello_world'
|
69
118
|
end
|
70
119
|
end
|
71
120
|
end
|
@@ -1,27 +1,25 @@
|
|
1
1
|
module Pacto
|
2
2
|
describe ResponseAdapter do
|
3
3
|
let(:response) do
|
4
|
-
double(
|
4
|
+
double(
|
5
5
|
:code => 200,
|
6
6
|
:headers => {'foo' => ['bar', 'baz'], 'hello' => ['world']},
|
7
7
|
:body => double('body')
|
8
|
-
|
8
|
+
)
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
@response_adapter = described_class.new(response)
|
13
|
-
end
|
11
|
+
subject(:response_adapter) { described_class.new response }
|
14
12
|
|
15
|
-
it '
|
16
|
-
|
13
|
+
it 'has a status' do
|
14
|
+
expect(response_adapter.status).to eq response.code
|
17
15
|
end
|
18
16
|
|
19
|
-
it '
|
20
|
-
|
17
|
+
it 'has a body' do
|
18
|
+
expect(response_adapter.body).to eq response.body
|
21
19
|
end
|
22
20
|
|
23
|
-
it '
|
24
|
-
|
21
|
+
it 'normalizes headers values according to RFC2616' do
|
22
|
+
expect(response_adapter.headers).to eq({'foo' => 'bar,baz', 'hello' => 'world'})
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Pacto
|
2
|
-
|
2
|
+
describe Response do
|
3
3
|
let(:body_definition) do
|
4
|
-
{:type =>
|
4
|
+
{:type => 'object', :required => true, :properties => double('body definition properties')}
|
5
5
|
end
|
6
6
|
let(:definition) do
|
7
7
|
{
|
@@ -11,20 +11,20 @@ module Pacto
|
|
11
11
|
}
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
describe '#instantiate' do
|
15
|
+
let(:generated_body) { double('generated body') }
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
it 'instantiates a response with a body that matches the given definition' do
|
18
|
+
JSON::Generator.should_receive(:generate).
|
19
|
+
with(definition['body']).
|
20
|
+
and_return(generated_body)
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
response = described_class.new(definition).instantiate
|
23
|
+
expect(response.status).to eq definition['status']
|
24
|
+
expect(response.headers).to eq definition['headers']
|
25
|
+
expect(response.body).to eq generated_body
|
26
|
+
end
|
27
|
+
end
|
28
28
|
|
29
29
|
describe '#validate' do
|
30
30
|
let(:status) { 200 }
|
@@ -39,142 +39,142 @@ module Pacto
|
|
39
39
|
end
|
40
40
|
|
41
41
|
context 'when status, headers and body match' do
|
42
|
-
it '
|
42
|
+
it 'does not return any errors' do
|
43
43
|
JSON::Validator.should_receive(:fully_validate).
|
44
|
-
with(definition['body'], fake_response.body).
|
44
|
+
with(definition['body'], fake_response.body, :version => :draft3).
|
45
45
|
and_return([])
|
46
46
|
|
47
47
|
response = described_class.new(definition)
|
48
|
-
response.validate(fake_response).
|
48
|
+
expect(response.validate(fake_response)).to be_empty
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
context 'when body is a pure string and matches the description' do
|
53
53
|
let(:string_required) { true }
|
54
54
|
let(:body_definition) do
|
55
55
|
{ 'type' => 'string', 'required' => string_required }
|
56
56
|
end
|
57
|
-
let(:response_body) {
|
58
|
-
|
59
|
-
it '
|
57
|
+
let(:response_body) { 'a simple string' }
|
58
|
+
|
59
|
+
it 'does not validate using JSON Schema' do
|
60
60
|
response = described_class.new(definition)
|
61
|
-
|
61
|
+
|
62
62
|
JSON::Validator.should_not_receive(:fully_validate)
|
63
63
|
response.validate(fake_response)
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
context 'if required' do
|
67
|
-
it '
|
67
|
+
it 'does not return an error when body is a string' do
|
68
68
|
response = described_class.new(definition)
|
69
|
-
|
70
|
-
response.validate(fake_response).
|
69
|
+
|
70
|
+
expect(response.validate(fake_response)).to be_empty
|
71
71
|
end
|
72
|
-
|
73
|
-
it '
|
72
|
+
|
73
|
+
it 'returns an error when body is nil' do
|
74
74
|
response = described_class.new(definition)
|
75
|
-
|
75
|
+
|
76
76
|
fake_response.stub(:body).and_return(nil)
|
77
|
-
response.validate(fake_response).size.
|
77
|
+
expect(response.validate(fake_response).size).to eq 1
|
78
78
|
end
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
context 'if not required' do
|
82
82
|
let(:string_required) { false }
|
83
|
-
|
84
|
-
it '
|
83
|
+
|
84
|
+
it 'does not return an error when body is a string' do
|
85
85
|
response = described_class.new(definition)
|
86
|
-
|
87
|
-
response.validate(fake_response).
|
86
|
+
|
87
|
+
expect(response.validate(fake_response)).to be_empty
|
88
88
|
end
|
89
|
-
|
90
|
-
it '
|
89
|
+
|
90
|
+
it 'does not return an error when body is nil' do
|
91
91
|
response = described_class.new(definition)
|
92
|
-
|
92
|
+
|
93
93
|
fake_response.stub(:body).and_return(nil)
|
94
|
-
response.validate(fake_response).
|
94
|
+
expect(response.validate(fake_response)).to be_empty
|
95
95
|
end
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
context 'if contains pattern' do
|
99
99
|
let(:body_definition) do
|
100
100
|
{ 'type' => 'string', 'required' => string_required, 'pattern' => 'a.c' }
|
101
101
|
end
|
102
|
-
|
102
|
+
|
103
103
|
context 'body matches pattern' do
|
104
104
|
let(:response_body) { 'cabcd' }
|
105
|
-
|
106
|
-
it
|
105
|
+
|
106
|
+
it 'does not return an error' do
|
107
107
|
response = described_class.new(definition)
|
108
|
-
|
109
|
-
response.validate(fake_response).
|
108
|
+
|
109
|
+
expect(response.validate(fake_response)).to be_empty
|
110
110
|
end
|
111
111
|
end
|
112
|
-
|
112
|
+
|
113
113
|
context 'body does not match pattern' do
|
114
114
|
let(:response_body) { 'cabscd' }
|
115
|
-
|
116
|
-
it
|
115
|
+
|
116
|
+
it 'returns an error' do
|
117
117
|
response = described_class.new(definition)
|
118
|
-
|
119
|
-
response.validate(fake_response).size.
|
118
|
+
|
119
|
+
expect(response.validate(fake_response).size).to eq 1
|
120
120
|
end
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
end
|
124
124
|
end
|
125
|
-
|
125
|
+
|
126
126
|
context 'when status does not match' do
|
127
127
|
let(:status) { 500 }
|
128
128
|
|
129
|
-
it '
|
129
|
+
it 'returns a status error' do
|
130
130
|
JSON::Validator.should_not_receive(:fully_validate)
|
131
|
-
|
131
|
+
|
132
132
|
response = described_class.new(definition)
|
133
|
-
response.validate(fake_response).
|
133
|
+
expect(response.validate(fake_response)).to eq ["Invalid status: expected #{definition['status']} but got #{status}"]
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
137
|
context 'when headers do not match' do
|
138
138
|
let(:headers) { {'Content-Type' => 'text/html'} }
|
139
139
|
|
140
|
-
it '
|
140
|
+
it 'returns a header error' do
|
141
141
|
JSON::Validator.should_not_receive(:fully_validate)
|
142
142
|
|
143
143
|
response = described_class.new(definition)
|
144
|
-
response.validate(fake_response).
|
144
|
+
expect(response.validate(fake_response)).to eq ["Invalid headers: expected #{definition['headers'].inspect} to be a subset of #{headers.inspect}"]
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
148
148
|
context 'when headers are a subset of expected headers' do
|
149
149
|
let(:headers) { {'Content-Type' => 'application/json'} }
|
150
150
|
|
151
|
-
it '
|
151
|
+
it 'does not return any errors' do
|
152
152
|
JSON::Validator.stub(:fully_validate).and_return([])
|
153
153
|
|
154
154
|
response = described_class.new(definition)
|
155
|
-
response.validate(fake_response).
|
155
|
+
expect(response.validate(fake_response)).to be_empty
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
159
|
context 'when headers values match but keys have different case' do
|
160
160
|
let(:headers) { {'content-type' => 'application/json'} }
|
161
161
|
|
162
|
-
it '
|
162
|
+
it 'does not return any errors' do
|
163
163
|
JSON::Validator.stub(:fully_validate).and_return([])
|
164
164
|
|
165
165
|
response = described_class.new(definition)
|
166
|
-
response.validate(fake_response).
|
166
|
+
expect(response.validate(fake_response)).to be_empty
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
170
170
|
context 'when body does not match' do
|
171
171
|
let(:errors) { [double('error1'), double('error2')] }
|
172
172
|
|
173
|
-
it '
|
173
|
+
it 'returns a list of errors' do
|
174
174
|
JSON::Validator.stub(:fully_validate).and_return(errors)
|
175
175
|
|
176
176
|
response = described_class.new(definition)
|
177
|
-
response.validate(fake_response).
|
177
|
+
expect(response.validate(fake_response)).to eq errors
|
178
178
|
end
|
179
179
|
end
|
180
180
|
|
@@ -186,16 +186,16 @@ module Pacto
|
|
186
186
|
}
|
187
187
|
end
|
188
188
|
|
189
|
-
it '
|
189
|
+
it 'does not validate body' do
|
190
190
|
JSON::Validator.should_not_receive(:fully_validate)
|
191
|
-
|
191
|
+
described_class.new(definition)
|
192
192
|
end
|
193
193
|
|
194
|
-
it '
|
194
|
+
it 'gives no errors' do
|
195
195
|
response = described_class.new(definition)
|
196
|
-
response.validate(fake_response).
|
196
|
+
expect(response.validate(fake_response)).to be_empty
|
197
197
|
end
|
198
198
|
end
|
199
199
|
end
|
200
|
-
|
200
|
+
end
|
201
201
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Pacto::Server
|
2
|
+
describe PlaybackServlet do
|
3
|
+
let(:request) { double }
|
4
|
+
let(:response) { double('response', :status= => '', :[]= => '', :body= => '') }
|
5
|
+
|
6
|
+
it 'alters response data with recorded status' do
|
7
|
+
servlet = PlaybackServlet.new status: 200
|
8
|
+
servlet.do_GET(request, response)
|
9
|
+
expect(response).to have_received(:status=).with(200)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'alters reponse data with recorded headers' do
|
13
|
+
servlet = PlaybackServlet.new headers: {'Content-Type' => 'application/json'}
|
14
|
+
servlet.do_GET(request, response)
|
15
|
+
expect(response).to have_received(:[]=).with('Content-Type', 'application/json')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'alters reponse data with recorded ' do
|
19
|
+
servlet = PlaybackServlet.new body: 'recorded'
|
20
|
+
servlet.do_GET(request, response)
|
21
|
+
expect(response).to have_received(:body=).with('recorded')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|