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.
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