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
data/spec/spec_helper.rb
CHANGED
@@ -1 +1,18 @@
|
|
1
|
+
require 'coveralls_helper'
|
1
2
|
require 'pacto'
|
3
|
+
require 'pacto/server'
|
4
|
+
require 'stringio'
|
5
|
+
require 'should_not/rspec'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.expect_with :rspec do |c|
|
9
|
+
c.syntax = :expect
|
10
|
+
end
|
11
|
+
config.before(:each) do
|
12
|
+
provider = Pacto.configuration.provider
|
13
|
+
unless provider.respond_to? :reset!
|
14
|
+
provider.stub(:reset!)
|
15
|
+
end
|
16
|
+
Pacto.clear!
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"request": {
|
3
|
+
"method": "GET",
|
4
|
+
"path": "/hello",
|
5
|
+
"headers": {
|
6
|
+
"Accept": "application/json"
|
7
|
+
},
|
8
|
+
"params": {}
|
9
|
+
},
|
10
|
+
|
11
|
+
"response": {
|
12
|
+
"status": 200,
|
13
|
+
"headers": { "Content-Type": "application/json" },
|
14
|
+
"body": {
|
15
|
+
"type": "object",
|
16
|
+
"required": true,
|
17
|
+
"properties": {
|
18
|
+
"message": { "type": "string", "required": true }
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe Pacto::Hooks::ERBHook do
|
2
|
+
describe '#process' do
|
3
|
+
let(:req) {
|
4
|
+
OpenStruct.new({:headers => {'User-Agent' => 'abcd'}})
|
5
|
+
}
|
6
|
+
let(:converted_req) {
|
7
|
+
{'HEADERS' => {'User-Agent' => 'abcd'}}
|
8
|
+
}
|
9
|
+
let(:res) {
|
10
|
+
OpenStruct.new({:body => 'before'})
|
11
|
+
}
|
12
|
+
|
13
|
+
before do
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'no matching contracts' do
|
17
|
+
it 'binds the request' do
|
18
|
+
contracts = Set.new
|
19
|
+
mock_erb({ :req => converted_req })
|
20
|
+
described_class.new.process contracts, req, res
|
21
|
+
expect(res.body).to eq('after')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'one matching contract' do
|
26
|
+
it 'binds the request and the contract\'s values' do
|
27
|
+
contract = OpenStruct.new({:values => {:max => 'test'}})
|
28
|
+
contracts = Set.new([contract])
|
29
|
+
mock_erb({ :req => converted_req, :max => 'test'})
|
30
|
+
described_class.new.process contracts, req, res
|
31
|
+
expect(res.body).to eq('after')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'multiple matching contracts' do
|
36
|
+
it 'binds the request and the first contract\'s values' do
|
37
|
+
contract1 = OpenStruct.new({:values => {:max => 'test'}})
|
38
|
+
contract2 = OpenStruct.new({:values => {:mob => 'team'}})
|
39
|
+
res = OpenStruct.new({:body => 'before'})
|
40
|
+
mock_erb({ :req => converted_req, :max => 'test'})
|
41
|
+
contracts = Set.new([contract1, contract2])
|
42
|
+
described_class.new.process contracts, req, res
|
43
|
+
expect(res.body).to eq('after')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def mock_erb(hash)
|
49
|
+
Pacto::ERBProcessor.any_instance.should_receive(:process).with('before', hash).and_return('after')
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Pacto
|
2
|
+
describe Configuration do
|
3
|
+
subject(:configuration) { Configuration.new }
|
4
|
+
let(:contracts_path) { 'path_to_contracts' }
|
5
|
+
|
6
|
+
it 'sets the preprocessor by default to ERBProcessor' do
|
7
|
+
expect(configuration.preprocessor).to be_kind_of ERBProcessor
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'sets the postprocessor by default to HashMergeProcessor' do
|
11
|
+
expect(configuration.postprocessor).to be_kind_of HashMergeProcessor
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'sets the stub provider by default to BuiltIn' do
|
15
|
+
expect(configuration.provider).to be_kind_of Stubs::BuiltIn
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets strict matchers by default to true' do
|
19
|
+
expect(configuration.strict_matchers).to be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets contracts path by default to nil' do
|
23
|
+
expect(configuration.contracts_path).to be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'sets logger by default to Logger' do
|
27
|
+
expect(configuration.logger).to be_kind_of Logger
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'about logging' do
|
31
|
+
|
32
|
+
context 'when PACTO_DEBUG is enabled' do
|
33
|
+
around do |example|
|
34
|
+
ENV['PACTO_DEBUG'] = 'true'
|
35
|
+
example.run
|
36
|
+
ENV.delete 'PACTO_DEBUG'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sets the log level to debug' do
|
40
|
+
expect(configuration.logger.level).to eq :debug
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when PACTO_DEBUG is disabled' do
|
45
|
+
it 'sets the log level to default' do
|
46
|
+
expect(configuration.logger.level).to eq :error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -4,49 +4,18 @@ module Pacto
|
|
4
4
|
let(:contract_name) { 'contract' }
|
5
5
|
let(:contract_path) { File.join('spec', 'unit', 'data', "#{contract_name}.json") }
|
6
6
|
let(:file_pre_processor) { double('file_pre_processor') }
|
7
|
-
let(:file_content) { File.read(contract_path)}
|
7
|
+
let(:file_content) { File.read(contract_path) }
|
8
8
|
|
9
9
|
describe '.build_from_file' do
|
10
|
-
it '
|
10
|
+
it 'builds a contract given a JSON file path and a host' do
|
11
11
|
file_pre_processor.stub(:process).and_return(file_content)
|
12
|
-
described_class.build_from_file(contract_path, host, file_pre_processor).
|
13
|
-
should be_a_kind_of(Pacto::Contract)
|
12
|
+
expect(described_class.build_from_file(contract_path, host, file_pre_processor)).to be_a_kind_of(Pacto::Contract)
|
14
13
|
end
|
15
14
|
|
16
|
-
it '
|
15
|
+
it 'processes files using File Pre Processor module' do
|
17
16
|
file_pre_processor.should_receive(:process).with(file_content).and_return(file_content)
|
18
17
|
described_class.build_from_file(contract_path, host, file_pre_processor)
|
19
18
|
end
|
20
19
|
end
|
21
|
-
|
22
|
-
describe '.validate_contract' do
|
23
|
-
it 'should not raise error if contract is correct' do
|
24
|
-
expect {
|
25
|
-
definition = {
|
26
|
-
'request' => {
|
27
|
-
'method' => 'GET',
|
28
|
-
'path' => '/a/path',
|
29
|
-
'params' => {},
|
30
|
-
'headers' => {}
|
31
|
-
},
|
32
|
-
'response' => {
|
33
|
-
'status' => 200,
|
34
|
-
'headers' => {},
|
35
|
-
'body' => {
|
36
|
-
'type' => 'string',
|
37
|
-
'required' => true
|
38
|
-
}
|
39
|
-
}
|
40
|
-
}
|
41
|
-
described_class.validate_contract(definition, contract_path)
|
42
|
-
}.not_to raise_error
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'should raise InvalidContract if contract do not contain a Request' do
|
46
|
-
expect {
|
47
|
-
described_class.validate_contract({}, contract_path)
|
48
|
-
}.to raise_error(InvalidContract)
|
49
|
-
end
|
50
|
-
end
|
51
20
|
end
|
52
21
|
end
|
@@ -1,49 +1,77 @@
|
|
1
1
|
module Pacto
|
2
2
|
describe Contract do
|
3
|
-
let(:request)
|
4
|
-
let(:
|
3
|
+
let(:request) { double 'request' }
|
4
|
+
let(:request_signature) { double 'request_signature' }
|
5
|
+
let(:response) { double 'response' }
|
6
|
+
let(:provider) { double 'provider' }
|
7
|
+
let(:instantiated_response) { double 'instantiated response' }
|
5
8
|
|
6
|
-
|
9
|
+
subject(:contract) { described_class.new request, response }
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
before do
|
12
|
+
response.stub(:instantiate => instantiated_response)
|
13
|
+
Pacto.configuration.provider = provider
|
14
|
+
end
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
describe '#stub_contract!' do
|
17
|
+
it 'instantiates the response and registers a stub' do
|
18
|
+
response.should_receive :instantiate
|
19
|
+
provider.should_receive(:stub_request!).with request, instantiated_response
|
20
|
+
contract.stub_contract!
|
21
|
+
end
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
describe '#validate' do
|
25
|
+
before do
|
26
|
+
response.stub(:validate => validation_result)
|
27
|
+
request.stub(:execute => fake_response)
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
let(:validation_result) { double 'validation result' }
|
31
|
+
let(:fake_response) { double 'fake response' }
|
26
32
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
it 'validates the generated response' do
|
34
|
+
response.should_receive(:validate).with(fake_response, {})
|
35
|
+
expect(contract.validate).to eq validation_result
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns the result of the validation' do
|
39
|
+
expect(contract.validate).to eq validation_result
|
40
|
+
end
|
33
41
|
|
34
|
-
|
42
|
+
it 'generates the response' do
|
43
|
+
request.should_receive :execute
|
44
|
+
contract.validate
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when response gotten is provided' do
|
48
|
+
it 'does not generate the response' do
|
49
|
+
request.should_not_receive :execute
|
50
|
+
contract.validate fake_response
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
38
54
|
|
39
|
-
describe '#
|
40
|
-
let(:
|
41
|
-
|
55
|
+
describe '#matches?' do
|
56
|
+
let(:request_matcher) do
|
57
|
+
double('fake request matcher').tap do |matcher|
|
58
|
+
matcher.stub(:matches?) { |r| r == request_signature }
|
59
|
+
end
|
60
|
+
end
|
42
61
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
62
|
+
context 'when the contract is not stubbed' do
|
63
|
+
it 'returns false' do
|
64
|
+
expect(contract.matches? request_signature).to be_false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when the contract is stubbed' do
|
69
|
+
it 'returns true if it matches the request' do
|
70
|
+
provider.should_receive(:stub_request!).with(request, instantiated_response).and_return(request_matcher)
|
71
|
+
contract.stub_contract!
|
72
|
+
expect(contract.matches? request_signature).to be_true
|
73
|
+
expect(contract.matches? :anything).to be_false
|
74
|
+
end
|
47
75
|
end
|
48
76
|
end
|
49
77
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
describe Pacto do
|
2
|
+
describe '.configure' do
|
3
|
+
let(:contracts_path) { 'path_to_contracts' }
|
4
|
+
it 'allows preprocessor manual configuration' do
|
5
|
+
expect(Pacto.configuration.preprocessor).to_not be_nil
|
6
|
+
Pacto.configure do |c|
|
7
|
+
c.preprocessor = nil
|
8
|
+
end
|
9
|
+
expect(Pacto.configuration.preprocessor).to be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'allows contracts_path manual configuration' do
|
13
|
+
expect(Pacto.configuration.contracts_path).to be_nil
|
14
|
+
Pacto.configure do |c|
|
15
|
+
c.contracts_path = contracts_path
|
16
|
+
end
|
17
|
+
expect(Pacto.configuration.contracts_path).to eq(contracts_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'register a Pacto Callback' do
|
21
|
+
callback_block = Pacto::Callback.new { }
|
22
|
+
Pacto.configure do |c|
|
23
|
+
c.register_callback(callback_block)
|
24
|
+
end
|
25
|
+
expect(Pacto.configuration.callback).to eq(callback_block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
describe Pacto do
|
2
|
+
let(:tag) { 'contract_tag' }
|
3
|
+
let(:another_tag) { 'another_tag' }
|
4
|
+
let(:contract) { double('contract') }
|
5
|
+
let(:another_contract) { double('another_contract') }
|
6
|
+
|
7
|
+
after do
|
8
|
+
described_class.unregister_all!
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.register' do
|
12
|
+
context 'no tag' do
|
13
|
+
it 'registers the contract with the default tag' do
|
14
|
+
described_class.register_contract contract
|
15
|
+
expect(described_class.registered[:default]).to include(contract)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'one tag' do
|
20
|
+
it 'registers a contract under a given tag' do
|
21
|
+
described_class.register_contract(contract, tag)
|
22
|
+
expect(described_class.registered[tag]).to include(contract)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not duplicate a contract when it has already been registered with the same tag' do
|
26
|
+
described_class.register_contract(contract, tag)
|
27
|
+
described_class.register_contract(contract, tag)
|
28
|
+
expect(described_class.registered[tag]).to include(contract)
|
29
|
+
expect(described_class.registered[tag]).to have(1).items
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'multiple tags' do
|
34
|
+
it 'registers a contract using different tags' do
|
35
|
+
described_class.register_contract(contract, tag, another_tag)
|
36
|
+
expect(described_class.registered[tag]).to include(contract)
|
37
|
+
expect(described_class.registered[another_tag]).to include(contract)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'registers a tag with different contracts ' do
|
41
|
+
described_class.register_contract(contract, tag)
|
42
|
+
described_class.register_contract(another_contract, tag)
|
43
|
+
expect(described_class.registered[tag]).to include(contract, another_contract)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with a block' do
|
49
|
+
it 'has a compact syntax for registering multiple contracts' do
|
50
|
+
described_class.configure do |c|
|
51
|
+
c.register_contract 'new_api/create_item_v2', :item, :new
|
52
|
+
c.register_contract 'authentication', :default
|
53
|
+
c.register_contract 'list_items_legacy', :legacy
|
54
|
+
c.register_contract 'get_item_legacy', :legacy
|
55
|
+
end
|
56
|
+
expect(described_class.registered[:new]).to include('new_api/create_item_v2')
|
57
|
+
expect(described_class.registered[:default]).to include('authentication')
|
58
|
+
expect(described_class.registered[:legacy]).to include('list_items_legacy', 'get_item_legacy')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '.use' do
|
64
|
+
before do
|
65
|
+
described_class.register_contract(contract, tag)
|
66
|
+
described_class.register_contract(another_contract, :default)
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when a contract has been registered' do
|
70
|
+
let(:response_body) { double('response_body') }
|
71
|
+
|
72
|
+
it 'stubs a contract with default values' do
|
73
|
+
contract.should_receive(:stub_contract!)
|
74
|
+
another_contract.should_receive(:stub_contract!)
|
75
|
+
expect(described_class.use(tag)).to eq 2
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'stubs default contract if unused tag' do
|
79
|
+
another_contract.should_receive(:stub_contract!)
|
80
|
+
expect(described_class.use(another_tag)).to eq 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when contract has not been registered' do
|
85
|
+
it 'raises an argument error' do
|
86
|
+
described_class.unregister_all!
|
87
|
+
expect { described_class.use('unregistered') }.to raise_error ArgumentError
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '.unregister_all!' do
|
93
|
+
it 'unregisters all previously registered contracts' do
|
94
|
+
described_class.register_contract(contract, tag)
|
95
|
+
described_class.unregister_all!
|
96
|
+
expect(described_class.registered).to be_empty
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '.contract_for' do
|
101
|
+
let(:request_signature) { double('request signature') }
|
102
|
+
|
103
|
+
context 'when no contracts are found for a request' do
|
104
|
+
it 'returns an empty list' do
|
105
|
+
expect(described_class.contract_for request_signature).to be_empty
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when contracts are found for a request' do
|
110
|
+
let(:contracts_that_match) { create_contracts 2, true }
|
111
|
+
let(:contracts_that_dont_match) { create_contracts 3, false }
|
112
|
+
let(:all_contracts) { contracts_that_match + contracts_that_dont_match }
|
113
|
+
|
114
|
+
it 'returns the matching contracts' do
|
115
|
+
register_and_use all_contracts
|
116
|
+
expect(described_class.contract_for request_signature).to eq(contracts_that_match)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_contracts(total, matches)
|
122
|
+
total.times.map do
|
123
|
+
double('contract',
|
124
|
+
:stub_contract! => double('request matcher'),
|
125
|
+
:matches? => matches)
|
126
|
+
end.to_set
|
127
|
+
end
|
128
|
+
|
129
|
+
def register_and_use contracts
|
130
|
+
contracts.each { |contract| described_class.register_contract contract }
|
131
|
+
Pacto.use :default
|
132
|
+
end
|
133
|
+
end
|