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
@@ -0,0 +1,23 @@
1
+ module Pacto
2
+ describe ERBProcessor do
3
+ subject(:processor) { ERBProcessor.new }
4
+
5
+ describe '#process' do
6
+ let(:erb) { '2 + 2 = <%= 2 + 2 %>' }
7
+ let(:result) { '2 + 2 = 4' }
8
+
9
+ it 'returns the result of ERB' do
10
+ expect(processor.process(erb)).to eq result
11
+ end
12
+
13
+ it 'logs the erb processed' do
14
+ Logger.instance.should_receive(:debug).with("Processed contract: \"#{result}\"")
15
+ processor.process erb
16
+ end
17
+
18
+ it 'does not mess with pure JSONs' do
19
+ processor.process('{"property": ["one", "two, null"]}')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,30 +3,30 @@ module Pacto
3
3
  describe HashSubsetOf do
4
4
  describe '#subset_of?' do
5
5
  context 'when the other hash is the same' do
6
- it 'should return true' do
7
- {:a => 'a'}.should be_subset_of({:a => 'a'})
6
+ it 'returns true' do
7
+ expect({:a => 'a'}).to be_subset_of({:a => 'a'})
8
8
  end
9
9
  end
10
10
 
11
11
  context 'when the other hash is a subset' do
12
- it 'should return true' do
13
- {:a => 'a'}.should be_subset_of({:a => 'a', :b => 'b'})
12
+ it 'returns true' do
13
+ expect({:a => 'a'}).to be_subset_of({:a => 'a', :b => 'b'})
14
14
  end
15
15
  end
16
16
 
17
17
  context 'when the other hash is not a subset' do
18
- it 'should return false' do
19
- {:a => 'a'}.subset_of?({:a => 'b'}).should be_false
18
+ it 'returns false' do
19
+ expect({:a => 'a'}.subset_of?({:a => 'b'})).to be_false
20
20
  end
21
21
  end
22
22
  end
23
23
 
24
24
  describe '#normalize_keys' do
25
- it 'should turn keys into downcased strings' do
26
- {:A => 'a'}.normalize_keys.should == {'a' => 'a'}
27
- {:a => 'a'}.normalize_keys.should == {'a' => 'a'}
28
- {'A' => 'a'}.normalize_keys.should == {'a' => 'a'}
29
- {'a' => 'a'}.normalize_keys.should == {'a' => 'a'}
25
+ it 'turns keys into downcased strings' do
26
+ expect({:A => 'a'}.normalize_keys).to eq({'a' => 'a'})
27
+ expect({:a => 'a'}.normalize_keys).to eq({'a' => 'a'})
28
+ expect({'A' => 'a'}.normalize_keys).to eq({'a' => 'a'})
29
+ expect({'a' => 'a'}.normalize_keys).to eq({'a' => 'a'})
30
30
  end
31
31
  end
32
32
  end
@@ -0,0 +1,142 @@
1
+ module Pacto
2
+ describe Generator do
3
+ let(:record_host) {
4
+ 'http://example.com'
5
+ }
6
+ let(:request) do
7
+ Pacto::Request.new(record_host, {
8
+ 'method' => 'GET',
9
+ 'path' => '/abcd',
10
+ 'headers' => {
11
+ 'Content-Length' => [1234],
12
+ 'Via' => ['Some Proxy'],
13
+ 'User-Agent' => ['rspec']
14
+ },
15
+ 'params' => []
16
+ })
17
+ end
18
+ let(:response_adapter) do
19
+ Pacto::ResponseAdapter.new(
20
+ OpenStruct.new({
21
+ 'status' => 200,
22
+ 'headers' => {
23
+ 'Date' => [Time.now],
24
+ 'Server' => ['Fake Server'],
25
+ 'Content-Type' => ['application/json']
26
+ },
27
+ 'body' => double('dummy body')
28
+ })
29
+ )
30
+ end
31
+ let(:response_body_schema) { '{"message": "dummy generated schema"}' }
32
+ let(:version) { 'draft3' }
33
+ let(:schema_generator) { double('schema_generator') }
34
+ let(:validator) { double('validator') }
35
+ let(:request_file) { 'request.json' }
36
+ let(:generator) { described_class.new version, schema_generator, validator }
37
+
38
+ def pretty obj
39
+ MultiJson.encode(obj, :pretty => true).gsub(/^$\n/, '')
40
+ end
41
+
42
+ describe '#generate' do
43
+ let(:request_contract) {
44
+ double({
45
+ :request => request,
46
+ })
47
+ }
48
+ let(:generated_contract) { double('generated contract') }
49
+ before do
50
+ Pacto.should_receive(:build_from_file).with(request_file, record_host).and_return request_contract
51
+ request.should_receive(:execute).and_return response_adapter
52
+ end
53
+
54
+ it 'parses the request' do
55
+ generator.should_receive(:save).with(request_file, request, anything)
56
+ generator.generate request_file, record_host
57
+ end
58
+
59
+ it 'fetches a response' do
60
+ generator.should_receive(:save).with(request_file, anything, response_adapter)
61
+ generator.generate request_file, record_host
62
+ end
63
+
64
+ it 'saves the result' do
65
+ generator.should_receive(:save).with(request_file, request, response_adapter).and_return generated_contract
66
+ expect(generator.generate request_file, record_host).to eq(generated_contract)
67
+ end
68
+ end
69
+
70
+ describe '#save' do
71
+ context 'invalid schema' do
72
+ it 'raises an error if schema generation fails' do
73
+ JSON::SchemaGenerator.should_receive(:generate).and_raise ArgumentError.new('Could not generate schema')
74
+ expect { generator.save request_file, request, response_adapter }.to raise_error
75
+ end
76
+
77
+ it 'raises an error if the generated contract is invalid' do
78
+ JSON::SchemaGenerator.should_receive(:generate).and_return response_body_schema
79
+ validator.should_receive(:validate).and_raise InvalidContract.new('dummy error')
80
+ expect { generator.save request_file, request, response_adapter }.to raise_error
81
+ end
82
+ end
83
+
84
+ context 'valid schema' do
85
+ let(:raw_contract) {
86
+ JSON::SchemaGenerator.should_receive(:generate).with(request_file, response_adapter.body, 'draft3').and_return response_body_schema
87
+ validator.should_receive(:validate).and_return true
88
+ generator.save request_file, request, response_adapter
89
+ }
90
+ subject(:generated_contract) { JSON.parse raw_contract }
91
+
92
+ it 'sets the body to the generated json-schema' do
93
+ expect(subject['response']['body']).to eq(JSON.parse response_body_schema)
94
+ end
95
+
96
+ it 'sets the request attributes' do
97
+ generated_request = subject['request']
98
+ expect(generated_request['params']).to eq(request.params)
99
+ expect(generated_request['path']).to eq(request.path)
100
+ end
101
+
102
+ it 'keeps important request headers' do
103
+ saved_headers = subject['request']['headers']
104
+ expect(saved_headers.keys).to include 'User-Agent'
105
+ end
106
+
107
+ it 'filters informational request headers' do
108
+ saved_headers = subject['request']['headers']
109
+ expect(saved_headers).not_to include 'Date'
110
+ expect(saved_headers).not_to include 'Server'
111
+ expect(saved_headers).not_to include 'Content-Length'
112
+ expect(saved_headers).not_to include 'Connection'
113
+ end
114
+
115
+ it 'normalizes the request method' do
116
+ generated_request = subject['request']
117
+ expect(generated_request['method']).to eq(request.method.downcase.to_s)
118
+ end
119
+
120
+ it 'sets the response attributes' do
121
+ generated_response = subject['response']
122
+ expect(generated_response['status']).to eq(response_adapter.status)
123
+ end
124
+
125
+ it 'keeps important response headers' do
126
+ saved_headers = subject['response']['headers']
127
+ expect(saved_headers.keys).to include 'Content-Type'
128
+ end
129
+
130
+ it 'filters informational response headers' do
131
+ saved_headers = subject['response']['headers']
132
+ expect(saved_headers).not_to include 'Content-Length'
133
+ expect(saved_headers).not_to include 'Via'
134
+ end
135
+
136
+ it 'generates pretty JSON' do
137
+ expect(raw_contract).to eq(pretty(subject))
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,20 @@
1
+ module Pacto
2
+ describe HashMergeProcessor do
3
+ describe '#process' do
4
+ let(:response_body_string) { 'a simple string' }
5
+ let(:response_body_hash) {
6
+ {'a' => 'simple hash'}
7
+ }
8
+
9
+ it 'does not change contract if values is nil' do
10
+ expect(subject.process(response_body_string, nil)).to eq response_body_string
11
+ end
12
+
13
+ it 'merges response body with values' do
14
+ merged_body = {'a' => 'simple hash', 'b' => :key}
15
+ expect(subject.process(response_body_hash, {:b => :key})).to eq merged_body.to_s
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ module Pacto
2
+ describe Logger do
3
+ before do
4
+ logger.log logger_lib
5
+ end
6
+
7
+ subject(:logger) { described_class.instance }
8
+ let(:logger_lib) { ::Logger.new(StringIO.new) }
9
+
10
+ it 'delegates debug to the logger lib' do
11
+ logger_lib.should_receive(:debug)
12
+ logger.debug
13
+ end
14
+
15
+ it 'delegates info to the logger lib' do
16
+ logger_lib.should_receive(:info)
17
+ logger.info
18
+ end
19
+
20
+ it 'delegates warn to the logger lib' do
21
+ logger_lib.should_receive(:warn)
22
+ logger.warn
23
+ end
24
+
25
+ it 'delegates error to the logger lib' do
26
+ logger_lib.should_receive(:error)
27
+ logger.error
28
+ end
29
+
30
+ it 'delegates fatal to the logger lib' do
31
+ logger_lib.should_receive(:error)
32
+ logger.error
33
+ end
34
+
35
+ it 'has the default log level as error' do
36
+ expect(logger.level).to eq :error
37
+ end
38
+
39
+ it 'provides access to the log level' do
40
+ logger.level = :info
41
+ expect(logger.level).to eq :info
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ module Pacto
2
+ describe MetaSchema do
3
+ let(:valid_contract) do
4
+ <<-EOF
5
+ {
6
+ "request": {
7
+ "method": "GET",
8
+ "path": "/hello_world",
9
+ "headers": {
10
+ "Accept": "application/json"
11
+ },
12
+ "params": {}
13
+ },
14
+
15
+ "response": {
16
+ "status": 200,
17
+ "headers": {
18
+ "Content-Type": "application/json"
19
+ },
20
+ "body": {
21
+ "description": "A simple response",
22
+ "type": "object",
23
+ "properties": {
24
+ "message": {
25
+ "type": "string"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ EOF
32
+ end
33
+
34
+ let(:invalid_contract) do
35
+ <<-EOF
36
+ {
37
+ "request": {
38
+ "method": "GET",
39
+ "path": "/hello_world",
40
+ "headers": {
41
+ "Accept": "application/json"
42
+ },
43
+ "params": {}
44
+ }
45
+
46
+ }
47
+ EOF
48
+ end
49
+
50
+ subject(:schema) { MetaSchema.new }
51
+
52
+ describe 'when validating a contract against the master schema' do
53
+ context 'with a valid contract structure' do
54
+ it 'does not raise any exceptions' do
55
+ expect {
56
+ schema.validate(valid_contract)
57
+ }.to_not raise_error(Exception)
58
+ end
59
+ end
60
+
61
+ context 'with an invalid contract structure' do
62
+ it 'raises InvalidContract exception' do
63
+ expect {
64
+ schema.validate(invalid_contract)
65
+ }.to raise_error(InvalidContract)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,23 +1,41 @@
1
1
  describe Pacto do
2
- let(:contract_name) { 'contract' }
3
- let(:contract) { double('contract') }
4
2
 
5
- after do
6
- described_class.unregister_all!
3
+ around(:each) do |example|
4
+ $stdout = StringIO.new
5
+ example.run
6
+ $stdout = STDOUT
7
7
  end
8
8
 
9
- describe '.register' do
10
- context 'by default' do
11
- it 'should register a contract under a given name' do
12
- described_class.register(contract_name, contract)
13
- described_class.registered[contract_name].should == contract
9
+ def output
10
+ $stdout.string.strip
11
+ end
12
+
13
+ def mock_validation(errors)
14
+ expect(JSON::Validator).to receive(:fully_validate).with(any_args).and_return errors
15
+ end
16
+
17
+ describe '.validate_contract' do
18
+ context 'valid' do
19
+ it 'displays a success message and return true' do
20
+ mock_validation []
21
+ success = Pacto.validate_contract 'my_contract.json'
22
+ expect(output).to eq 'All contracts successfully meta-validated'
23
+ expect(success).to be_true
14
24
  end
15
25
  end
16
26
 
17
- context 'when a contract has already been registered with the same name' do
18
- it 'should raise an argument error' do
19
- described_class.register(contract_name, contract)
20
- expect { described_class.register(contract_name, contract) }.to raise_error(ArgumentError)
27
+ context 'invalid' do
28
+ it 'displays one error messages and return false' do
29
+ mock_validation ['Error 1']
30
+ success = Pacto.validate_contract 'my_contract.json'
31
+ expect(output).to match /error/
32
+ expect(success).to be_false
33
+ end
34
+
35
+ it 'displays several error messages and return false' do
36
+ mock_validation ['Error 1', 'Error 2']
37
+ success = Pacto.validate_contract 'my_contract.json'
38
+ expect(success).to be_false
21
39
  end
22
40
  end
23
41
  end
@@ -35,52 +53,8 @@ describe Pacto do
35
53
 
36
54
  it 'returns whatever the factory returns' do
37
55
  Pacto::ContractFactory.stub(:build_from_file => instantiated_contract)
38
- described_class.build_from_file(path, host, file_pre_processor).should == instantiated_contract
56
+ expect(described_class.build_from_file(path, host, file_pre_processor)).to eq instantiated_contract
39
57
  end
40
58
  end
41
59
 
42
- describe '.use' do
43
- before do
44
- described_class.register(contract_name, contract)
45
- end
46
-
47
- context 'by default' do
48
- let(:instantiated_contract) { double('instantiated contract', :response_body => response_body)}
49
- let(:response_body) { double('response_body') }
50
-
51
- before do
52
- described_class.registered[contract_name].stub(:instantiate => instantiated_contract)
53
- instantiated_contract.stub(:stub!)
54
- end
55
-
56
- it 'should instantiate a contract with default values' do
57
- described_class.registered[contract_name].should_receive(:instantiate).with(nil).and_return(instantiated_contract)
58
- described_class.use(contract_name)
59
- end
60
-
61
- it 'should return the instantiated contract' do
62
- described_class.use(contract_name).should == instantiated_contract
63
- end
64
-
65
- it 'should stub further requests with the instantiated contract' do
66
- instantiated_contract.should_receive(:stub!)
67
- described_class.use(contract_name)
68
- end
69
-
70
- end
71
-
72
- context 'when contract has not been registered' do
73
- it 'should raise an argument error' do
74
- expect { described_class.use('unregistered') }.to raise_error ArgumentError
75
- end
76
- end
77
- end
78
-
79
- describe '.unregister_all!' do
80
- it 'should unregister all previously registered contracts' do
81
- described_class.register(contract_name, contract)
82
- described_class.unregister_all!
83
- described_class.registered.should be_empty
84
- end
85
- end
86
60
  end