pacto 0.2.5 → 0.3.0.pre
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.
- 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
|