pacto 0.3.1 → 0.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +29 -7
  4. data/.travis.yml +8 -1
  5. data/CONTRIBUTING.md +3 -6
  6. data/Gemfile +13 -2
  7. data/Guardfile +4 -4
  8. data/Procfile +1 -0
  9. data/README.md +47 -13
  10. data/Rakefile +66 -19
  11. data/TODO.md +33 -10
  12. data/bin/pacto +4 -0
  13. data/changelog.md +30 -0
  14. data/docs/configuration.md +69 -0
  15. data/docs/consumer.md +18 -0
  16. data/docs/cops.md +39 -0
  17. data/docs/forensics.md +66 -0
  18. data/docs/generation.md +65 -0
  19. data/docs/rake_tasks.md +10 -0
  20. data/docs/rspec.md +0 -0
  21. data/docs/samples.md +133 -0
  22. data/docs/server.md +34 -0
  23. data/docs/server_cli.md +18 -0
  24. data/docs/stenographer.md +20 -0
  25. data/features/configuration/strict_matchers.feature +10 -10
  26. data/features/evolve/existing_services.feature +12 -10
  27. data/features/generate/generation.feature +11 -11
  28. data/features/steps/pacto_steps.rb +17 -12
  29. data/features/stub/templates.feature +4 -4
  30. data/features/support/env.rb +21 -9
  31. data/features/validate/meta_validation.feature +9 -17
  32. data/features/validate/validation.feature +5 -6
  33. data/lib/pacto.rb +41 -33
  34. data/lib/pacto/actor.rb +5 -0
  35. data/lib/pacto/actors/from_examples.rb +67 -0
  36. data/lib/pacto/actors/json_generator.rb +20 -0
  37. data/lib/pacto/cli.rb +75 -0
  38. data/lib/pacto/cli/helpers.rb +20 -0
  39. data/lib/pacto/consumer.rb +80 -0
  40. data/lib/pacto/consumer/faraday_driver.rb +34 -0
  41. data/lib/pacto/contract.rb +48 -20
  42. data/lib/pacto/contract_builder.rb +125 -0
  43. data/lib/pacto/contract_factory.rb +31 -12
  44. data/lib/pacto/contract_files.rb +1 -0
  45. data/lib/pacto/contract_set.rb +12 -0
  46. data/lib/pacto/cops.rb +46 -0
  47. data/lib/pacto/cops/body_cop.rb +23 -0
  48. data/lib/pacto/cops/request_body_cop.rb +10 -0
  49. data/lib/pacto/cops/response_body_cop.rb +10 -0
  50. data/lib/pacto/{validators/response_header_validator.rb → cops/response_header_cop.rb} +9 -15
  51. data/lib/pacto/cops/response_status_cop.rb +18 -0
  52. data/lib/pacto/core/configuration.rb +16 -5
  53. data/lib/pacto/core/contract_registry.rb +13 -32
  54. data/lib/pacto/core/hook.rb +1 -0
  55. data/lib/pacto/core/http_middleware.rb +23 -0
  56. data/lib/pacto/core/investigation_registry.rb +60 -0
  57. data/lib/pacto/core/modes.rb +1 -0
  58. data/lib/pacto/core/pacto_request.rb +59 -0
  59. data/lib/pacto/core/pacto_response.rb +41 -0
  60. data/lib/pacto/dash.rb +9 -0
  61. data/lib/pacto/erb_processor.rb +1 -0
  62. data/lib/pacto/exceptions/invalid_contract.rb +1 -0
  63. data/lib/pacto/extensions.rb +3 -16
  64. data/lib/pacto/forensics/investigation_filter.rb +90 -0
  65. data/lib/pacto/forensics/investigation_matcher.rb +80 -0
  66. data/lib/pacto/generator.rb +31 -53
  67. data/lib/pacto/generator/filters.rb +8 -7
  68. data/lib/pacto/generator/hint.rb +26 -0
  69. data/lib/pacto/generator/native_contract_generator.rb +74 -0
  70. data/lib/pacto/hooks/erb_hook.rb +2 -1
  71. data/lib/pacto/investigation.rb +49 -0
  72. data/lib/pacto/logger.rb +1 -0
  73. data/lib/pacto/meta_schema.rb +12 -6
  74. data/lib/pacto/native_contract_factory.rb +60 -0
  75. data/lib/pacto/observers/stenographer.rb +42 -0
  76. data/lib/pacto/provider.rb +27 -0
  77. data/lib/pacto/rake_task.rb +25 -70
  78. data/lib/pacto/request_clause.rb +31 -29
  79. data/lib/pacto/request_pattern.rb +20 -3
  80. data/lib/pacto/resettable.rb +22 -0
  81. data/lib/pacto/response_clause.rb +5 -12
  82. data/lib/pacto/rspec.rb +38 -31
  83. data/lib/pacto/server.rb +4 -0
  84. data/lib/pacto/stubs/uri_pattern.rb +21 -11
  85. data/lib/pacto/stubs/webmock_adapter.rb +69 -34
  86. data/lib/pacto/swagger_contract_factory.rb +90 -0
  87. data/lib/pacto/test_helper.rb +37 -0
  88. data/lib/pacto/ui.rb +32 -2
  89. data/lib/pacto/uri.rb +2 -1
  90. data/lib/pacto/version.rb +2 -1
  91. data/pacto-server.gemspec +24 -0
  92. data/pacto.gemspec +13 -9
  93. data/resources/contract_schema.json +46 -18
  94. data/resources/draft-04.json +150 -0
  95. data/sample_apis/album/cover_api.rb +12 -0
  96. data/sample_apis/config.ru +25 -0
  97. data/sample_apis/echo_api.rb +26 -0
  98. data/sample_apis/files_api.rb +50 -0
  99. data/sample_apis/hello_api.rb +14 -0
  100. data/sample_apis/ping_api.rb +11 -0
  101. data/sample_apis/reverse_api.rb +20 -0
  102. data/samples/README.md +11 -0
  103. data/samples/Rakefile +2 -0
  104. data/samples/configuration.rb +33 -0
  105. data/samples/consumer.rb +15 -0
  106. data/samples/contracts/README.md +1 -0
  107. data/samples/contracts/contract.js +93 -0
  108. data/samples/contracts/get_album_cover.json +48 -0
  109. data/samples/contracts/localhost/api/echo.json +37 -0
  110. data/samples/contracts/localhost/api/ping.json +38 -0
  111. data/samples/cops.rb +30 -0
  112. data/samples/forensics.rb +54 -0
  113. data/samples/generation.rb +48 -0
  114. data/samples/rake_tasks.sh +7 -0
  115. data/samples/rspec.rb +1 -0
  116. data/samples/samples.rb +92 -0
  117. data/samples/scripts/bootstrap +2 -0
  118. data/samples/scripts/wrapper +11 -0
  119. data/samples/server.rb +24 -0
  120. data/samples/server_cli.sh +12 -0
  121. data/samples/stenographer.rb +17 -0
  122. data/spec/coveralls_helper.rb +1 -0
  123. data/spec/fabricators/contract_fabricator.rb +94 -0
  124. data/spec/fabricators/http_fabricator.rb +48 -0
  125. data/spec/fabricators/webmock_fabricator.rb +24 -0
  126. data/spec/{unit/data → fixtures/contracts}/contract.json +2 -2
  127. data/spec/fixtures/contracts/contract_with_examples.json +58 -0
  128. data/spec/{unit/data → fixtures/contracts}/simple_contract.json +5 -3
  129. data/spec/{integration/data → fixtures/contracts}/strict_contract.json +5 -3
  130. data/spec/{integration/data → fixtures/contracts}/templating_contract.json +3 -2
  131. data/spec/{integration/data/simple_contract.json → fixtures/deprecated_contracts/deprecated_contract.json} +2 -1
  132. data/spec/fixtures/swagger/petstore.yaml +101 -0
  133. data/spec/integration/e2e_spec.rb +19 -20
  134. data/spec/integration/forensics/integration_matcher_spec.rb +90 -0
  135. data/spec/integration/rspec_spec.rb +22 -25
  136. data/spec/integration/templating_spec.rb +7 -6
  137. data/spec/pacto/dummy_server.rb +4 -0
  138. data/spec/pacto/{server → dummy_server}/dummy.rb +7 -6
  139. data/spec/pacto/dummy_server/jruby_workaround_helper.rb +23 -0
  140. data/spec/pacto/{server → dummy_server}/playback_servlet.rb +3 -2
  141. data/spec/spec_helper.rb +16 -7
  142. data/spec/unit/actors/from_examples_spec.rb +70 -0
  143. data/spec/unit/actors/json_generator_spec.rb +105 -0
  144. data/spec/unit/pacto/actor_spec.rb +23 -0
  145. data/spec/unit/pacto/configuration_spec.rb +7 -6
  146. data/spec/unit/pacto/consumer/faraday_driver_spec.rb +40 -0
  147. data/spec/unit/pacto/contract_builder_spec.rb +89 -0
  148. data/spec/unit/pacto/contract_factory_spec.rb +62 -11
  149. data/spec/unit/pacto/contract_files_spec.rb +1 -0
  150. data/spec/unit/pacto/contract_set_spec.rb +36 -0
  151. data/spec/unit/pacto/contract_spec.rb +51 -39
  152. data/spec/unit/pacto/cops/body_cop_spec.rb +107 -0
  153. data/spec/unit/pacto/{validators/response_header_validator_spec.rb → cops/response_header_cop_spec.rb} +30 -19
  154. data/spec/unit/pacto/cops/response_status_cop_spec.rb +26 -0
  155. data/spec/unit/pacto/cops_spec.rb +75 -0
  156. data/spec/unit/pacto/core/configuration_spec.rb +6 -5
  157. data/spec/unit/pacto/core/contract_registry_spec.rb +16 -83
  158. data/spec/unit/pacto/core/http_middleware_spec.rb +36 -0
  159. data/spec/unit/pacto/core/investigation_spec.rb +62 -0
  160. data/spec/unit/pacto/core/modes_spec.rb +5 -4
  161. data/spec/unit/pacto/erb_processor_spec.rb +3 -2
  162. data/spec/unit/pacto/extensions_spec.rb +10 -20
  163. data/spec/unit/pacto/generator/filters_spec.rb +11 -10
  164. data/spec/unit/pacto/generator/native_contract_generator_spec.rb +171 -0
  165. data/spec/unit/{hooks → pacto/hooks}/erb_hook_spec.rb +18 -11
  166. data/spec/unit/pacto/investigation_registry_spec.rb +77 -0
  167. data/spec/unit/pacto/logger_spec.rb +6 -5
  168. data/spec/unit/pacto/meta_schema_spec.rb +5 -4
  169. data/spec/unit/pacto/native_contract_factory_spec.rb +26 -0
  170. data/spec/unit/pacto/pacto_spec.rb +13 -28
  171. data/spec/unit/pacto/request_clause_spec.rb +16 -51
  172. data/spec/unit/pacto/request_pattern_spec.rb +6 -5
  173. data/spec/unit/pacto/response_clause_spec.rb +6 -19
  174. data/spec/unit/pacto/server/playback_servlet_spec.rb +21 -18
  175. data/spec/unit/pacto/stubs/observers/stenographer_spec.rb +33 -0
  176. data/spec/unit/pacto/stubs/uri_pattern_spec.rb +39 -11
  177. data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +67 -117
  178. data/spec/unit/pacto/swagger_contract_factory_spec.rb +56 -0
  179. data/spec/unit/pacto/uri_spec.rb +1 -0
  180. data/tasks/release.rake +57 -0
  181. metadata +247 -76
  182. data/.rubocop-todo.yml +0 -24
  183. data/.ruby-gemset +0 -1
  184. data/.ruby-version +0 -1
  185. data/CHANGELOG +0 -12
  186. data/features/validate/body_only.feature +0 -85
  187. data/lib/pacto/contract_list.rb +0 -17
  188. data/lib/pacto/contract_validator.rb +0 -29
  189. data/lib/pacto/core/validation_registry.rb +0 -40
  190. data/lib/pacto/stubs/webmock_helper.rb +0 -69
  191. data/lib/pacto/validation.rb +0 -54
  192. data/lib/pacto/validators/body_validator.rb +0 -49
  193. data/lib/pacto/validators/request_body_validator.rb +0 -26
  194. data/lib/pacto/validators/response_body_validator.rb +0 -26
  195. data/lib/pacto/validators/response_status_validator.rb +0 -24
  196. data/spec/pacto/server.rb +0 -2
  197. data/spec/unit/pacto/contract_list_spec.rb +0 -35
  198. data/spec/unit/pacto/contract_validator_spec.rb +0 -85
  199. data/spec/unit/pacto/core/validation_registry_spec.rb +0 -76
  200. data/spec/unit/pacto/core/validation_spec.rb +0 -60
  201. data/spec/unit/pacto/generator_spec.rb +0 -132
  202. data/spec/unit/pacto/stubs/webmock_helper_spec.rb +0 -20
  203. data/spec/unit/pacto/validators/body_validator_spec.rb +0 -118
  204. data/spec/unit/pacto/validators/response_status_validator_spec.rb +0 -20
@@ -0,0 +1,36 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Core
4
+ describe HTTPMiddleware do
5
+ subject(:middleware) { Pacto::Core::HTTPMiddleware.new }
6
+ let(:request) { double }
7
+ let(:response) { double }
8
+
9
+ class FailingObserver
10
+ def raise_error(_pacto_request, _pacto_response)
11
+ fail InvalidContract, ['The contract was missing things', 'and stuff']
12
+ end
13
+ end
14
+
15
+ describe '#process' do
16
+ it 'calls registered HTTP observers' do
17
+ observer1, observer2 = double, double
18
+ expect(observer1).to receive(:respond_to?).with(:do_something).and_return true
19
+ expect(observer2).to receive(:respond_to?).with(:do_something_else).and_return true
20
+ middleware.add_observer(observer1, :do_something)
21
+ middleware.add_observer(observer2, :do_something_else)
22
+ expect(observer1).to receive(:do_something).with(request, response)
23
+ expect(observer2).to receive(:do_something_else).with(request, response)
24
+ middleware.process request, response
25
+ end
26
+
27
+ pending 'logs rescues and logs failures'
28
+ pending 'calls the HTTP middleware'
29
+ pending 'calls the registered hook'
30
+ pending 'calls generate when generate is enabled'
31
+ pending 'calls validate when validate mode is enabled'
32
+ pending 'validates a WebMock request/response pair'
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ describe Investigation do
4
+ let(:request) { double('request') }
5
+ let(:response) { double('response') }
6
+ let(:contract) { Fabricate(:contract) }
7
+ let(:investigation_citations) { [] }
8
+ let(:investigation_citations_with_errors) { ['an error occurred'] }
9
+
10
+ it 'stores the request, response, contract and citations' do
11
+ investigation = Pacto::Investigation.new request, response, contract, investigation_citations
12
+ expect(investigation.request).to eq request
13
+ expect(investigation.response).to eq response
14
+ expect(investigation.contract).to eq contract
15
+ expect(investigation.citations).to eq investigation_citations
16
+ end
17
+
18
+ context 'if there were investigation errors' do
19
+ subject(:investigation) do
20
+ Pacto::Investigation.new request, response, contract, investigation_citations_with_errors
21
+ end
22
+
23
+ describe '#successful?' do
24
+ it 'returns false' do
25
+ expect(investigation.successful?).to be_falsey
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'if there were no investigation errors' do
31
+ subject(:investigation) do
32
+ Pacto::Investigation.new request, response, contract, investigation_citations
33
+ end
34
+
35
+ it 'returns false' do
36
+ expect(investigation.successful?).to be true
37
+ end
38
+ end
39
+
40
+ describe '#against_contract?' do
41
+ it 'returns nil if there was no contract' do
42
+ investigation = Pacto::Investigation.new request, response, nil, investigation_citations
43
+ expect(investigation.against_contract? 'a').to be_nil
44
+ end
45
+
46
+ it 'returns the contract with an exact string name match' do
47
+ allow(contract).to receive(:file).and_return('foo')
48
+ investigation = Pacto::Investigation.new request, response, contract, investigation_citations
49
+ expect(investigation.against_contract? 'foo').to eq(contract)
50
+ expect(investigation.against_contract? 'bar').to be_nil
51
+ end
52
+
53
+ it 'returns the contract if there is a regex match' do
54
+ allow(contract).to receive(:file).and_return 'foobar'
55
+ investigation = Pacto::Investigation.new request, response, contract, investigation_citations
56
+ expect(investigation.against_contract?(/foo/)).to eq(contract)
57
+ expect(investigation.against_contract?(/bar/)).to eq(contract)
58
+ expect(investigation.against_contract?(/baz/)).to be_nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,17 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  describe Pacto do
2
- modes = %w{generate validate}
3
+ modes = %w(generate validate)
3
4
  modes.each do |mode|
4
5
  enable_method = "#{mode}!".to_sym # generate!
5
6
  query_method = "#{mode[0..-2]}ing?".to_sym # generating?
6
7
  disable_method = "stop_#{mode[0..-2]}ing!".to_sym # stop_generating!
7
8
  describe ".#{mode}!" do
8
9
  it "tells the provider to enable #{mode} mode" do
9
- expect(subject.send query_method).to be_false
10
+ expect(subject.send query_method).to be_falsey
10
11
  subject.send enable_method
11
- expect(subject.send query_method).to be_true
12
+ expect(subject.send query_method).to be true
12
13
 
13
14
  subject.send disable_method
14
- expect(subject.send query_method).to be_false
15
+ expect(subject.send query_method).to be_falsey
15
16
  end
16
17
  end
17
18
  end
@@ -1,6 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Pacto
2
3
  describe ERBProcessor do
3
- subject(:processor) { ERBProcessor.new }
4
+ subject(:processor) { described_class.new }
4
5
 
5
6
  describe '#process' do
6
7
  let(:erb) { '2 + 2 = <%= 2 + 2 %>' }
@@ -11,7 +12,7 @@ module Pacto
11
12
  end
12
13
 
13
14
  it 'logs the erb processed' do
14
- Pacto.configuration.logger.should_receive(:debug).with("Processed contract: \"#{result}\"")
15
+ expect(Pacto.configuration.logger).to receive(:debug).with("Processed contract: \"#{result}\"")
15
16
  processor.process erb
16
17
  end
17
18
 
@@ -1,24 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Pacto
2
- module Extensions
3
- describe HashSubsetOf do
4
- describe '#normalize_keys' do
5
- it 'turns keys into downcased strings' do
6
- expect({:A => 'a'}.normalize_keys).to eq('a' => 'a')
7
- expect({:a => 'a'}.normalize_keys).to eq('a' => 'a')
8
- expect({'A' => 'a'}.normalize_keys).to eq('a' => 'a')
9
- expect({'a' => 'a'}.normalize_keys).to eq('a' => 'a')
10
- end
11
- end
12
-
13
- describe '#normalize_header_keys' do
14
- it 'matches headers to the style in the RFC documentation' do
15
- expect(Pacto::Extensions.normalize_header_keys(:'user-agent' => 'a')).to eq('User-Agent' => 'a') # rubocop:disable SymbolName
16
- expect(Pacto::Extensions.normalize_header_keys(:user_agent => 'a')).to eq('User-Agent' => 'a')
17
- expect(Pacto::Extensions.normalize_header_keys('User-Agent' => 'a')).to eq('User-Agent' => 'a')
18
- expect(Pacto::Extensions.normalize_header_keys('user-agent' => 'a')).to eq('User-Agent' => 'a')
19
- expect(Pacto::Extensions.normalize_header_keys('user_agent' => 'a')).to eq('User-Agent' => 'a')
20
- expect(Pacto::Extensions.normalize_header_keys('USER_AGENT' => 'a')).to eq('User-Agent' => 'a')
21
- end
3
+ describe Extensions do
4
+ describe '#normalize_header_keys' do
5
+ it 'matches headers to the style in the RFC documentation' do
6
+ expect(Pacto::Extensions.normalize_header_keys(:'user-agent' => 'a')).to eq('User-Agent' => 'a') # rubocop:disable SymbolName
7
+ expect(Pacto::Extensions.normalize_header_keys(user_agent: 'a')).to eq('User-Agent' => 'a')
8
+ expect(Pacto::Extensions.normalize_header_keys('User-Agent' => 'a')).to eq('User-Agent' => 'a')
9
+ expect(Pacto::Extensions.normalize_header_keys('user-agent' => 'a')).to eq('User-Agent' => 'a')
10
+ expect(Pacto::Extensions.normalize_header_keys('user_agent' => 'a')).to eq('User-Agent' => 'a')
11
+ expect(Pacto::Extensions.normalize_header_keys('USER_AGENT' => 'a')).to eq('User-Agent' => 'a')
22
12
  end
23
13
  end
24
14
  end
@@ -1,22 +1,23 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Pacto
2
- class Generator
3
+ module Generator
3
4
  describe Filters do
4
5
  let(:record_host) do
5
6
  'http://example.com'
6
7
  end
7
8
  let(:request) do
8
9
  RequestClause.new(
9
- record_host,
10
- 'method' => 'GET',
11
- 'path' => '/abcd',
12
- 'headers' => {
10
+ host: record_host,
11
+ http_method: 'GET',
12
+ path: '/abcd',
13
+ headers: {
13
14
  'Server' => ['example.com'],
14
15
  'Connection' => ['Close'],
15
16
  'Content-Length' => [1234],
16
17
  'Via' => ['Some Proxy'],
17
18
  'User-Agent' => ['rspec']
18
19
  },
19
- 'params' => {
20
+ params: {
20
21
  'apikey' => "<%= ENV['MY_API_KEY'] %>"
21
22
  }
22
23
  )
@@ -24,8 +25,8 @@ module Pacto
24
25
  let(:varies) { ['User-Agent'] }
25
26
  let(:response) do
26
27
  Faraday::Response.new(
27
- :status => 200,
28
- :response_headers => {
28
+ status: 200,
29
+ response_headers: {
29
30
  'Date' => Time.now.rfc2822,
30
31
  'Last-Modified' => Time.now.rfc2822,
31
32
  'ETag' => 'abc123',
@@ -33,7 +34,7 @@ module Pacto
33
34
  'Content-Type' => ['application/json'],
34
35
  'Vary' => varies
35
36
  },
36
- :body => double('dummy body')
37
+ body: double('dummy body')
37
38
  )
38
39
  end
39
40
 
@@ -63,7 +64,7 @@ module Pacto
63
64
  end
64
65
  context 'as multiple items' do
65
66
  let(:varies) do
66
- %w{User-Agent Via}
67
+ %w(User-Agent Via)
67
68
  end
68
69
  it 'keeps each header' do
69
70
  expect(filtered_request_headers).to include 'user-agent'
@@ -0,0 +1,171 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Generator
4
+ describe NativeContractGenerator do
5
+ let(:record_host) do
6
+ 'http://example.com'
7
+ end
8
+ let(:request_clause) { Fabricate(:request_clause, params: { 'api_key' => "<%= ENV['MY_API_KEY'] %>" }) }
9
+ let(:response_adapter) do
10
+ Faraday::Response.new(
11
+ status: 200,
12
+ response_headers: {
13
+ 'Date' => [Time.now],
14
+ 'Server' => ['Fake Server'],
15
+ 'Content-Type' => ['application/json'],
16
+ 'Vary' => ['User-Agent']
17
+ },
18
+ body: 'dummy body' # body is just a string
19
+ )
20
+ end
21
+ let(:filtered_request_headers) { double('filtered_response_headers') }
22
+ let(:filtered_response_headers) { double('filtered_response_headers') }
23
+ let(:response_body_schema) { '{"message": "dummy generated schema"}' }
24
+ let(:version) { 'draft3' }
25
+ let(:schema_generator) { double('schema_generator') }
26
+ let(:validator) { double('validator') }
27
+ let(:filters) { double :filters }
28
+ let(:consumer) { double 'consumer' }
29
+ let(:request_file) { 'request.json' }
30
+ let(:generator) { described_class.new version, schema_generator, validator, filters, consumer }
31
+ let(:request_contract) do
32
+ Fabricate(:partial_contract, request: request_clause, file: request_file)
33
+ end
34
+ let(:request) do
35
+ Pacto.configuration.default_consumer.build_request request_contract
36
+ end
37
+
38
+ def pretty(obj)
39
+ MultiJson.encode(obj, pretty: true).gsub(/^$\n/, '')
40
+ end
41
+
42
+ describe '#generate_from_partial_contract' do
43
+ # TODO: Deprecate partial contracts?
44
+ let(:generated_contract) { Fabricate(:contract) }
45
+ before do
46
+ expect(Pacto).to receive(:load_contract).with(request_file, record_host).and_return request_contract
47
+ expect(consumer).to receive(:request).with(request_contract).and_return([request, response_adapter])
48
+ end
49
+
50
+ it 'parses the request' do
51
+ expect(generator).to receive(:save).with(request_file, request, anything)
52
+ generator.generate_from_partial_contract request_file, record_host
53
+ end
54
+
55
+ it 'fetches a response' do
56
+ expect(generator).to receive(:save).with(request_file, anything, response_adapter)
57
+ generator.generate_from_partial_contract request_file, record_host
58
+ end
59
+
60
+ it 'saves the result' do
61
+ expect(generator).to receive(:save).with(request_file, request, response_adapter).and_return generated_contract
62
+ expect(generator.generate_from_partial_contract request_file, record_host).to eq(generated_contract)
63
+ end
64
+ end
65
+
66
+ describe '#save' do
67
+ before do
68
+ allow(filters).to receive(:filter_request_headers).with(request, response_adapter).and_return filtered_request_headers
69
+ allow(filters).to receive(:filter_response_headers).with(request, response_adapter).and_return filtered_response_headers
70
+ end
71
+ context 'invalid schema' do
72
+ it 'raises an error if schema generation fails' do
73
+ expect(schema_generator).to 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
+ expect(schema_generator).to receive(:generate).and_return response_body_schema
79
+ expect(validator).to 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) do
86
+ expect(schema_generator).to receive(:generate).with(request_file, response_adapter.body, Pacto.configuration.generator_options).and_return response_body_schema
87
+ expect(validator).to receive(:validate).and_return true
88
+ generator.save request_file, request, response_adapter
89
+ end
90
+ subject(:generated_contract) { JSON.parse raw_contract }
91
+
92
+ it 'sets the schema to the generated json-schema' do
93
+ expect(subject['response']['schema']).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.uri.query_values)
99
+ expect(generated_request['path']).to eq(request.uri.path)
100
+ end
101
+
102
+ it 'preserves ERB in the request params' do
103
+ generated_request = subject['request']
104
+ expect(generated_request['params']).to eq('api_key' => "<%= ENV['MY_API_KEY'] %>")
105
+ end
106
+
107
+ it 'normalizes the request method' do
108
+ generated_request = subject['request']
109
+ expect(generated_request['http_method']).to eq(request.method.downcase.to_s)
110
+ end
111
+
112
+ it 'sets the response attributes' do
113
+ generated_response = subject['response']
114
+ expect(generated_response['status']).to eq(response_adapter.status)
115
+ end
116
+
117
+ it 'generates pretty JSON' do
118
+ expect(raw_contract).to eq(pretty(subject))
119
+ end
120
+ end
121
+
122
+ context 'with hints' do
123
+ let(:request1) { Fabricate(:pacto_request, host: 'example.com', path: '/album/5/cover') }
124
+ let(:request2) { Fabricate(:pacto_request, host: 'example.com', path: '/album/7/cover') }
125
+ let(:response1) { Fabricate(:pacto_response) }
126
+ let(:response2) { Fabricate(:pacto_response) }
127
+ let(:contracts_path) { Dir.mktmpdir }
128
+
129
+ before(:each) do
130
+ allow(filters).to receive(:filter_request_headers).with(request1, response1).and_return request1.headers
131
+ allow(filters).to receive(:filter_response_headers).with(request1, response1).and_return response1.headers
132
+ allow(filters).to receive(:filter_request_headers).with(request2, response2).and_return request2.headers
133
+ allow(filters).to receive(:filter_response_headers).with(request2, response2).and_return response2.headers
134
+ allow(schema_generator).to receive(:generate).with(request_file, response1.body, Pacto.configuration.generator_options).and_return response_body_schema
135
+ allow(schema_generator).to receive(:generate).with(request_file, response2.body, Pacto.configuration.generator_options).and_return response_body_schema
136
+ allow(validator).to receive(:validate).twice.and_return true
137
+ Pacto.configuration.contracts_path = contracts_path
138
+ Pacto::Generator.configure do |c|
139
+ c.hint 'Get Album Cover', http_method: :get, host: 'http://example.com', path: '/album/{id}/cover', target_file: 'album_services/get_album_cover.json'
140
+ end
141
+ Pacto.generate!
142
+ end
143
+
144
+ it 'names the contract based on the hint' do
145
+ contract1 = generator.generate request1, response1
146
+ expect(contract1.name).to eq('Get Album Cover')
147
+ end
148
+
149
+ it 'sets the path to match the hint' do
150
+ contract1 = generator.generate request1, response1
151
+ expect(contract1.request.path).to eq('/album/{id}/cover')
152
+ end
153
+
154
+ it 'sets the target file based on the hint' do
155
+ contract1 = generator.generate request1, response1
156
+ expected_path = File.expand_path('album_services/get_album_cover.json', contracts_path)
157
+ real_expected_path = Pathname.new(expected_path).realpath.to_s
158
+ expected_file_uri = Addressable::URI.convert_path(real_expected_path).to_s
159
+ expect(contract1.file).to eq(expected_file_uri)
160
+ end
161
+
162
+ xit 'does not create duplicate contracts' do
163
+ contract1 = generator.generate request1, response1
164
+ contract2 = generator.generate request2, response2
165
+ expect(contract1).to eq(contract2)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -1,13 +1,17 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  describe Pacto::Hooks::ERBHook do
2
3
  describe '#process' do
3
4
  let(:req) do
4
- OpenStruct.new(:headers => {'User-Agent' => 'abcd'})
5
+ OpenStruct.new(headers: { 'User-Agent' => 'abcd' })
5
6
  end
6
7
  let(:converted_req) do
7
- {'HEADERS' => {'User-Agent' => 'abcd'}}
8
+ { 'HEADERS' => { 'User-Agent' => 'abcd' } }
8
9
  end
9
10
  let(:res) do
10
- OpenStruct.new(:body => 'before')
11
+ Pacto::PactoResponse.new(
12
+ status: 200,
13
+ body: 'before'
14
+ )
11
15
  end
12
16
 
13
17
  before do
@@ -16,7 +20,7 @@ describe Pacto::Hooks::ERBHook do
16
20
  context 'no matching contracts' do
17
21
  it 'binds the request' do
18
22
  contracts = Set.new
19
- mock_erb(:req => converted_req)
23
+ mock_erb(req: converted_req)
20
24
  described_class.new.process contracts, req, res
21
25
  expect(res.body).to eq('after')
22
26
  end
@@ -24,9 +28,9 @@ describe Pacto::Hooks::ERBHook do
24
28
 
25
29
  context 'one matching contract' do
26
30
  it 'binds the request and the contract\'s values' do
27
- contract = OpenStruct.new(:values => {:max => 'test'})
31
+ contract = OpenStruct.new(values: { max: 'test' })
28
32
  contracts = Set.new([contract])
29
- mock_erb(:req => converted_req, :max => 'test')
33
+ mock_erb(req: converted_req, max: 'test')
30
34
  described_class.new.process contracts, req, res
31
35
  expect(res.body).to eq('after')
32
36
  end
@@ -34,10 +38,13 @@ describe Pacto::Hooks::ERBHook do
34
38
 
35
39
  context 'multiple matching contracts' do
36
40
  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
+ contract1 = OpenStruct.new(values: { max: 'test' })
42
+ contract2 = OpenStruct.new(values: { mob: 'team' })
43
+ res = Pacto::PactoResponse.new(
44
+ status: 200,
45
+ body: 'before'
46
+ )
47
+ mock_erb(req: converted_req, max: 'test')
41
48
  contracts = Set.new([contract1, contract2])
42
49
  described_class.new.process contracts, req, res
43
50
  expect(res.body).to eq('after')
@@ -46,6 +53,6 @@ describe Pacto::Hooks::ERBHook do
46
53
  end
47
54
 
48
55
  def mock_erb(hash)
49
- Pacto::ERBProcessor.any_instance.should_receive(:process).with('before', hash).and_return('after')
56
+ expect_any_instance_of(Pacto::ERBProcessor).to receive(:process).with('before', hash).and_return('after')
50
57
  end
51
58
  end