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.
Files changed (70) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +0 -2
  3. data/.rubocop-todo.yml +51 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +4 -2
  6. data/Guardfile +28 -14
  7. data/README.md +81 -51
  8. data/Rakefile +24 -11
  9. data/features/generation/generation.feature +25 -0
  10. data/features/journeys/validation.feature +74 -0
  11. data/features/support/env.rb +16 -0
  12. data/lib/pacto.rb +63 -34
  13. data/lib/pacto/contract.rb +25 -11
  14. data/lib/pacto/contract_factory.rb +13 -44
  15. data/lib/pacto/core/callback.rb +11 -0
  16. data/lib/pacto/core/configuration.rb +34 -0
  17. data/lib/pacto/core/contract_repository.rb +44 -0
  18. data/lib/pacto/erb_processor.rb +18 -0
  19. data/lib/pacto/exceptions/invalid_contract.rb +10 -1
  20. data/lib/pacto/extensions.rb +2 -2
  21. data/lib/pacto/generator.rb +75 -0
  22. data/lib/pacto/hash_merge_processor.rb +14 -0
  23. data/lib/pacto/hooks/erb_hook.rb +17 -0
  24. data/lib/pacto/logger.rb +42 -0
  25. data/lib/pacto/meta_schema.rb +17 -0
  26. data/lib/pacto/rake_task.rb +75 -12
  27. data/lib/pacto/request.rb +3 -4
  28. data/lib/pacto/response.rb +27 -19
  29. data/lib/pacto/server.rb +2 -0
  30. data/lib/pacto/server/dummy.rb +45 -0
  31. data/lib/pacto/server/playback_servlet.rb +21 -0
  32. data/lib/pacto/stubs/built_in.rb +57 -0
  33. data/lib/pacto/version.rb +1 -1
  34. data/pacto.gemspec +8 -2
  35. data/resources/contract_schema.json +216 -0
  36. data/spec/coveralls_helper.rb +10 -0
  37. data/spec/integration/data/strict_contract.json +33 -0
  38. data/spec/integration/data/templating_contract.json +25 -0
  39. data/spec/integration/e2e_spec.rb +40 -7
  40. data/spec/integration/templating_spec.rb +55 -0
  41. data/spec/spec_helper.rb +17 -0
  42. data/spec/unit/data/simple_contract.json +22 -0
  43. data/spec/unit/hooks/erb_hook_spec.rb +51 -0
  44. data/spec/unit/pacto/configuration_spec.rb +51 -0
  45. data/spec/unit/pacto/contract_factory_spec.rb +4 -35
  46. data/spec/unit/pacto/contract_spec.rb +59 -31
  47. data/spec/unit/pacto/core/configuration_spec.rb +28 -0
  48. data/spec/unit/pacto/core/contract_repository_spec.rb +133 -0
  49. data/spec/unit/pacto/erb_processor_spec.rb +23 -0
  50. data/spec/unit/pacto/extensions_spec.rb +11 -11
  51. data/spec/unit/pacto/generator_spec.rb +142 -0
  52. data/spec/unit/pacto/hash_merge_processor_spec.rb +20 -0
  53. data/spec/unit/pacto/logger_spec.rb +44 -0
  54. data/spec/unit/pacto/meta_schema_spec.rb +70 -0
  55. data/spec/unit/pacto/pacto_spec.rb +32 -58
  56. data/spec/unit/pacto/request_spec.rb +83 -34
  57. data/spec/unit/pacto/response_adapter_spec.rb +9 -11
  58. data/spec/unit/pacto/response_spec.rb +68 -68
  59. data/spec/unit/pacto/server/playback_servlet_spec.rb +24 -0
  60. data/spec/unit/pacto/stubs/built_in_spec.rb +168 -0
  61. metadata +291 -147
  62. data/.rspec_integration +0 -4
  63. data/.rspec_unit +0 -4
  64. data/lib/pacto/file_pre_processor.rb +0 -12
  65. data/lib/pacto/instantiated_contract.rb +0 -62
  66. data/spec/integration/spec_helper.rb +0 -1
  67. data/spec/integration/utils/dummy_server.rb +0 -34
  68. data/spec/unit/pacto/file_pre_processor_spec.rb +0 -13
  69. data/spec/unit/pacto/instantiated_contract_spec.rb +0 -224
  70. 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 'should build a contract given a JSON file path and a host' do
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 'should process files using File Pre Processor module' do
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) { double('request') }
4
- let(:response) { double('response') }
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
- let(:contract) { described_class.new(request, response) }
9
+ subject(:contract) { described_class.new request, response }
7
10
 
8
- describe '#instantiate' do
9
- let(:instantiated_response) { double('instantiated response') }
10
- let(:instantiated_contract) { double('instantiated contract') }
11
+ before do
12
+ response.stub(:instantiate => instantiated_response)
13
+ Pacto.configuration.provider = provider
14
+ end
11
15
 
12
- context 'by default' do
13
- it 'should instantiate a contract with default attributes' do
14
- response.should_receive(:instantiate).and_return(instantiated_response)
15
- InstantiatedContract.should_receive(:new).
16
- with(request, instantiated_response).
17
- and_return(instantiated_contract)
18
- instantiated_contract.should_not_receive(:replace!)
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
- contract.instantiate.should == instantiated_contract
21
- end
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
- context 'with extra attributes' do
25
- let(:attributes) { {:foo => 'bar'} }
30
+ let(:validation_result) { double 'validation result' }
31
+ let(:fake_response) { double 'fake response' }
26
32
 
27
- it 'should instantiate a contract and overwrite default attributes' do
28
- response.should_receive(:instantiate).and_return(instantiated_response)
29
- InstantiatedContract.should_receive(:new).
30
- with(request, instantiated_response).
31
- and_return(instantiated_contract)
32
- instantiated_contract.should_receive(:replace!).with(attributes)
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
- contract.instantiate(attributes).should == instantiated_contract
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 '#validate' do
40
- let(:fake_response) { double('fake response') }
41
- let(:validation_result) { double('validation result') }
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
- it 'should execute the request and match it against the expected response' do
44
- request.should_receive(:execute).and_return(fake_response)
45
- response.should_receive(:validate).with(fake_response).and_return(validation_result)
46
- contract.validate.should == validation_result
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