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/lib/pacto/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pacto
2
- VERSION = "0.2.5"
2
+ VERSION = '0.3.0.pre'
3
3
  end
data/pacto.gemspec CHANGED
@@ -19,17 +19,23 @@ Gem::Specification.new do |gem|
19
19
  gem.require_paths = ["lib"]
20
20
 
21
21
  gem.add_dependency "webmock"
22
- gem.add_dependency "json"
23
- gem.add_dependency "json-schema", "1.0.4"
22
+ gem.add_dependency "multi_json"
23
+ gem.add_dependency "json-schema", "~> 2.0"
24
24
  gem.add_dependency "json-generator"
25
25
  gem.add_dependency "hash-deep-merge"
26
26
  gem.add_dependency "httparty"
27
27
  gem.add_dependency "addressable"
28
28
  gem.add_dependency "coveralls"
29
+ gem.add_dependency "json-schema-generator"
29
30
 
30
31
  gem.add_development_dependency "rake"
31
32
  gem.add_development_dependency "rspec"
33
+ gem.add_development_dependency "should_not"
34
+ gem.add_development_dependency "aruba"
32
35
  gem.add_development_dependency "guard-rspec"
36
+ gem.add_development_dependency "rubocop"
37
+ gem.add_development_dependency "guard-rubocop"
38
+ gem.add_development_dependency "guard-cucumber"
33
39
  gem.add_development_dependency "rb-fsevent" if RUBY_PLATFORM =~ /darwin/i
34
40
  gem.add_development_dependency "terminal-notifier-guard" if RUBY_PLATFORM =~ /darwin/i
35
41
  end
@@ -0,0 +1,216 @@
1
+ {
2
+ "title": "Example Schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "request": {
6
+ "type": "object",
7
+ "required": true,
8
+ "properties": {
9
+ "method": {
10
+ "type": "string",
11
+ "required": true
12
+ },
13
+ "path": {
14
+ "type": "string",
15
+ "required": true
16
+ },
17
+ "headers": {
18
+ "type": "object",
19
+ "required": false
20
+ },
21
+ "params": {
22
+ "type": "object",
23
+ "required": false
24
+ }
25
+ }
26
+ },
27
+ "response": {
28
+ "type": "object",
29
+ "required": true,
30
+ "properties": {
31
+ "status":{
32
+ "type": "integer",
33
+ "required": true
34
+ },
35
+ "body": {
36
+ "$ref": "http://json-schema.org/draft-03/schema#"
37
+ }
38
+ }
39
+ },
40
+ "json-schema": {
41
+ "_comment": "this should be refactored to not need the whole schema here",
42
+ "$schema": "http://json-schema.org/draft-03/schema#",
43
+ "id": "http://json-schema.org/draft-03/schema#",
44
+ "type": "object",
45
+
46
+ "properties": {
47
+ "type": {
48
+ "type": [ "string", "array" ],
49
+ "items": {
50
+ "type": [ "string", { "$ref": "#" } ]
51
+ },
52
+ "uniqueItems": true,
53
+ "default": "any"
54
+ },
55
+
56
+ "properties": {
57
+ "type": "object",
58
+ "additionalProperties": { "$ref": "#" },
59
+ "default": {}
60
+ },
61
+
62
+ "patternProperties": {
63
+ "type": "object",
64
+ "additionalProperties": { "$ref": "#" },
65
+ "default": {}
66
+ },
67
+
68
+ "additionalProperties": {
69
+ "type": [ { "$ref": "#" }, "boolean" ],
70
+ "default": {}
71
+ },
72
+
73
+ "items": {
74
+ "type": [ { "$ref": "#" }, "array" ],
75
+ "items": { "$ref": "#" },
76
+ "default": {}
77
+ },
78
+
79
+ "additionalItems": {
80
+ "type": [ { "$ref": "#" }, "boolean" ],
81
+ "default": {}
82
+ },
83
+
84
+ "required": {
85
+ "type": "boolean",
86
+ "default": false
87
+ },
88
+
89
+ "dependencies": {
90
+ "type": "object",
91
+ "additionalProperties": {
92
+ "type": [ "string", "array", { "$ref": "#" } ],
93
+ "items": {
94
+ "type": "string"
95
+ }
96
+ },
97
+ "default": {}
98
+ },
99
+
100
+ "minimum": {
101
+ "type": "number"
102
+ },
103
+
104
+ "maximum": {
105
+ "type": "number"
106
+ },
107
+
108
+ "exclusiveMinimum": {
109
+ "type": "boolean",
110
+ "default": false
111
+ },
112
+
113
+ "exclusiveMaximum": {
114
+ "type": "boolean",
115
+ "default": false
116
+ },
117
+
118
+ "minItems": {
119
+ "type": "integer",
120
+ "minimum": 0,
121
+ "default": 0
122
+ },
123
+
124
+ "maxItems": {
125
+ "type": "integer",
126
+ "minimum": 0
127
+ },
128
+
129
+ "uniqueItems": {
130
+ "type": "boolean",
131
+ "default": false
132
+ },
133
+
134
+ "pattern": {
135
+ "type": "string",
136
+ "format": "regex"
137
+ },
138
+
139
+ "minLength": {
140
+ "type": "integer",
141
+ "minimum": 0,
142
+ "default": 0
143
+ },
144
+
145
+ "maxLength": {
146
+ "type": "integer"
147
+ },
148
+
149
+ "enum": {
150
+ "type": "array",
151
+ "minItems": 1,
152
+ "uniqueItems": true
153
+ },
154
+
155
+ "default": {
156
+ "type": "any"
157
+ },
158
+
159
+ "title": {
160
+ "type": "string"
161
+ },
162
+
163
+ "description": {
164
+ "type": "string"
165
+ },
166
+
167
+ "format": {
168
+ "type": "string"
169
+ },
170
+
171
+ "divisibleBy": {
172
+ "type": "number",
173
+ "minimum": 0,
174
+ "exclusiveMinimum": true,
175
+ "default": 1
176
+ },
177
+
178
+ "disallow": {
179
+ "type": [ "string", "array" ],
180
+ "items": {
181
+ "type": [ "string", { "$ref": "#" } ]
182
+ },
183
+ "uniqueItems": true
184
+ },
185
+
186
+ "extends": {
187
+ "type": [ { "$ref": "#" }, "array" ],
188
+ "items": { "$ref": "#" },
189
+ "default": {}
190
+ },
191
+
192
+ "id": {
193
+ "type": "string",
194
+ "format": "uri"
195
+ },
196
+
197
+ "$ref": {
198
+ "type": "string",
199
+ "format": "uri"
200
+ },
201
+
202
+ "$schema": {
203
+ "type": "string",
204
+ "format": "uri"
205
+ }
206
+ },
207
+
208
+ "dependencies": {
209
+ "exclusiveMinimum": "minimum",
210
+ "exclusiveMaximum": "maximum"
211
+ },
212
+
213
+ "default": {}
214
+ }
215
+ }
216
+ }
@@ -0,0 +1,10 @@
1
+ unless ENV['NO_COVERAGE']
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ Coveralls::SimpleCov::Formatter
8
+ ]
9
+ SimpleCov.start
10
+ end
@@ -0,0 +1,33 @@
1
+ {
2
+ "request": {
3
+ "method": "GET",
4
+ "path": "/strict",
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
+ "devices": {
19
+ "type": "array",
20
+ "minItems": 2,
21
+ "items": {
22
+ "type": "string",
23
+ "required": true,
24
+ "default": "/dev/<%= device_id %>",
25
+ "pattern": "^/dev/[^/]+(/[^/]+)*$"
26
+ },
27
+ "required": true,
28
+ "uniqueItems": true
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "request": {
3
+ "method": "GET",
4
+ "path": "/echo",
5
+ "headers": {
6
+ "Accept": "application/json",
7
+ "Custom-Auth-Token": "<%= auth_token %>",
8
+ "X-Message": "<%= key %>"
9
+ },
10
+ "params": {
11
+ }
12
+ },
13
+
14
+ "response": {
15
+ "status": 200,
16
+ "headers": { "Content-Type": "application/json" },
17
+ "body": {
18
+ "type": "object",
19
+ "required": true,
20
+ "properties": {
21
+ "message": { "type": "string", "default": "<%= req['HEADERS']['X-Message'].reverse %>", "required": true }
22
+ }
23
+ }
24
+ }
25
+ }
@@ -1,5 +1,6 @@
1
1
  describe 'Pacto' do
2
2
  let(:contract_path) { 'spec/integration/data/simple_contract.json' }
3
+ let(:strict_contract_path) { 'spec/integration/data/strict_contract.json' }
3
4
 
4
5
  before :all do
5
6
  WebMock.allow_net_connect!
@@ -7,7 +8,7 @@ describe 'Pacto' do
7
8
 
8
9
  context 'Contract validation' do
9
10
  before :all do
10
- @server = DummyServer.new 8000, '/hello', '{"message": "Hello World!"}'
11
+ @server = Pacto::Server::Dummy.new 8000, '/hello', '{"message": "Hello World!"}'
11
12
  @server.start
12
13
  end
13
14
 
@@ -17,22 +18,54 @@ describe 'Pacto' do
17
18
 
18
19
  it 'verifies the contract against a producer' do
19
20
  contract = Pacto.build_from_file(contract_path, 'http://localhost:8000')
20
- contract.validate.should == []
21
+ expect(contract.validate).to be_empty
21
22
  end
22
23
  end
23
24
 
24
25
  context 'Stub generation' do
25
26
  it 'generates a stub to be used by a consumer' do
26
27
  contract = Pacto.build_from_file(contract_path, 'http://dummyprovider.com')
27
- Pacto.register('my_contract', contract)
28
- Pacto.use('my_contract')
29
- response.keys.should == ['message']
30
- response['message'].should be_kind_of(String)
28
+ Pacto.register_contract(contract, 'my_tag')
29
+ Pacto.use('my_tag')
30
+ expect(response.keys).to eq ['message']
31
+ expect(response['message']).to be_kind_of(String)
31
32
  end
32
33
 
33
34
  let :response do
34
35
  raw_response = HTTParty.get('http://dummyprovider.com/hello', headers: {'Accept' => 'application/json' })
35
- JSON.parse(raw_response.body)
36
+ MultiJson.load(raw_response.body)
37
+ end
38
+ end
39
+
40
+ context 'Journey' do
41
+ it 'stubs multiple services with a single use' do
42
+ Pacto.configure do |c|
43
+ c.strict_matchers = false
44
+ c.postprocessor = Pacto::ERBProcessor.new
45
+ c.preprocessor = nil
46
+ c.register_callback Pacto::Hooks::ERBHook.new
47
+ end
48
+
49
+ # Preprocessor must be off before building!
50
+ login_contract = Pacto.build_from_file(contract_path, 'http://dummyprovider.com')
51
+ contract = Pacto.build_from_file(strict_contract_path, 'http://dummyprovider.com')
52
+ Pacto.configure do |c|
53
+ c.register_contract login_contract, :default
54
+ c.register_contract contract, :devices
55
+ end
56
+
57
+ Pacto.use(:devices, {:device_id => 42})
58
+
59
+ raw_response = HTTParty.get('http://dummyprovider.com/hello', headers: {'Accept' => 'application/json' })
60
+ login_response = MultiJson.load(raw_response.body)
61
+ expect(login_response.keys).to eq ['message']
62
+ expect(login_response['message']).to be_kind_of(String)
63
+
64
+ devices_response = HTTParty.get('http://dummyprovider.com/strict', headers: {'Accept' => 'application/json' })
65
+ devices_response = MultiJson.load(devices_response.body)
66
+ expect(devices_response['devices']).to have(2).items
67
+ expect(devices_response['devices'][0]).to eq '/dev/42'
68
+ # devices_response['devices'][1].should == '/dev/43'
36
69
  end
37
70
  end
38
71
  end
@@ -0,0 +1,55 @@
1
+ require 'securerandom'
2
+ require 'pacto/erb_processor'
3
+
4
+ describe 'Templating' do
5
+ let(:contract_path) { 'spec/integration/data/templating_contract.json' }
6
+
7
+ let(:key) { SecureRandom.hex }
8
+ let(:auth_token) { SecureRandom.hex }
9
+ let :response do
10
+ contract = Pacto.build_from_file(contract_path, 'http://dummyprovider.com')
11
+ Pacto.register_contract(contract, 'my_contract')
12
+ Pacto.use('my_contract', {:key => key, :auth_token => auth_token})
13
+
14
+ raw_response = HTTParty.get('http://dummyprovider.com/echo', headers: {
15
+ 'Accept' => 'application/json',
16
+ 'Custom-Auth-Token' => "#{auth_token}",
17
+ 'X-Message' => "#{key}"
18
+ }
19
+ )
20
+ MultiJson.load(raw_response.body)
21
+ end
22
+
23
+ before :each do
24
+ Pacto.unregister_all!
25
+ end
26
+
27
+ context 'No processing' do
28
+ it 'does not proccess erb tag' do
29
+ Pacto.configure do |c|
30
+ c.preprocessor = nil
31
+ c.postprocessor = nil
32
+ c.strict_matchers = false
33
+ c.register_callback do |contracts, req, res|
34
+ res
35
+ end
36
+ end
37
+
38
+ expect(response.keys).to eq ['message']
39
+ expect(response['message']).to eq("<%= req['HEADERS']['X-Message'].reverse %>")
40
+ end
41
+ end
42
+
43
+ context 'Post processing' do
44
+ it 'processes erb on each request' do
45
+ Pacto.configure do |c|
46
+ c.preprocessor = nil
47
+ c.strict_matchers = false
48
+ c.postprocessor = Pacto::ERBProcessor.new
49
+ end
50
+
51
+ expect(response.keys).to eq ['message']
52
+ expect(response['message']).to eq(key.reverse)
53
+ end
54
+ end
55
+ end