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
@@ -7,7 +7,7 @@ Feature: Validation
7
7
  """
8
8
  {
9
9
  "request": {
10
- "method": "GET",
10
+ "http_method": "GET",
11
11
  "path": "/hello",
12
12
  "headers": { "Accept": "application/json" },
13
13
  "params": {}
@@ -16,7 +16,7 @@ Feature: Validation
16
16
  "response": {
17
17
  "status": 200,
18
18
  "headers": { "Content-Type": "application/json" },
19
- "body": {
19
+ "schema": {
20
20
  "description": "A simple response",
21
21
  "type": "object",
22
22
  "properties": {
@@ -27,10 +27,9 @@ Feature: Validation
27
27
  }
28
28
  """
29
29
  When I successfully run `bundle exec rake pacto:validate['http://localhost:8000','tmp/aruba/contracts/simple_contract.json']`
30
- Then the output should contain:
30
+ Then the stdout should contain:
31
31
  """"
32
- Validating contracts in directory tmp/aruba/contracts/simple_contract.json against host http://localhost:8000
33
-
34
- simple_contract.json: OK!
32
+ Validating contracts against host http://localhost:8000
33
+ OK! simple_contract.json
35
34
  1 valid contract
36
35
  """
@@ -1,9 +1,10 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'pacto/version'
2
3
 
3
4
  require 'addressable/template'
5
+ require 'swagger'
4
6
  require 'middleware'
5
7
  require 'faraday'
6
- require 'hash_deep_merge'
7
8
  require 'multi_json'
8
9
  require 'json-schema'
9
10
  require 'json-generator'
@@ -12,45 +13,53 @@ require 'ostruct'
12
13
  require 'erb'
13
14
  require 'logger'
14
15
 
16
+ require 'pacto/dash'
17
+ require 'pacto/resettable'
18
+ require 'pacto/exceptions/invalid_contract.rb'
15
19
  require 'pacto/logger'
16
20
  require 'pacto/ui'
21
+ require 'pacto/request_pattern'
22
+ require 'pacto/core/http_middleware'
23
+ require 'pacto/consumer/faraday_driver'
24
+ require 'pacto/actor'
25
+ require 'pacto/consumer'
26
+ require 'pacto/provider'
27
+ require 'pacto/actors/json_generator'
28
+ require 'pacto/actors/from_examples'
29
+ require 'pacto/core/pacto_request'
30
+ require 'pacto/core/pacto_response'
17
31
  require 'pacto/core/contract_registry'
18
- require 'pacto/core/validation_registry'
32
+ require 'pacto/core/investigation_registry'
19
33
  require 'pacto/core/configuration'
20
34
  require 'pacto/core/modes'
21
35
  require 'pacto/core/hook'
22
- require 'pacto/exceptions/invalid_contract.rb'
23
36
  require 'pacto/extensions'
24
37
  require 'pacto/request_clause'
25
38
  require 'pacto/response_clause'
26
39
  require 'pacto/stubs/webmock_adapter'
27
40
  require 'pacto/stubs/uri_pattern'
28
41
  require 'pacto/contract'
29
- require 'pacto/contract_validator'
30
- require 'pacto/contract_factory'
31
- require 'pacto/validation'
42
+ require 'pacto/cops'
32
43
  require 'pacto/meta_schema'
44
+ require 'pacto/contract_factory'
45
+ require 'pacto/investigation'
33
46
  require 'pacto/hooks/erb_hook'
47
+ require 'pacto/observers/stenographer'
34
48
  require 'pacto/generator'
35
49
  require 'pacto/generator/filters'
36
50
  require 'pacto/contract_files'
37
- require 'pacto/contract_list'
38
- require 'pacto/request_pattern'
51
+ require 'pacto/contract_set'
39
52
  require 'pacto/uri'
40
53
 
41
- # Validators
42
- require 'pacto/validators/body_validator'
43
- require 'pacto/validators/request_body_validator'
44
- require 'pacto/validators/response_body_validator'
45
- require 'pacto/validators/response_status_validator'
46
- require 'pacto/validators/response_header_validator'
54
+ # Cops
55
+ require 'pacto/cops/body_cop'
56
+ require 'pacto/cops/request_body_cop'
57
+ require 'pacto/cops/response_body_cop'
58
+ require 'pacto/cops/response_status_cop'
59
+ require 'pacto/cops/response_header_cop'
47
60
 
48
61
  module Pacto
49
62
  class << self
50
- def contract_factory
51
- @factory = ContractFactory.new
52
- end
53
-
54
63
  def configuration
55
64
  @configuration ||= Configuration.new
56
65
  end
@@ -59,12 +68,18 @@ module Pacto
59
68
  @registry ||= ContractRegistry.new
60
69
  end
61
70
 
71
+ # Resets data and metrics only. It usually makes sense to call this between test scenarios.
72
+ def reset
73
+ Pacto::InvestigationRegistry.instance.reset!
74
+ # Pacto::Resettable.reset_all
75
+ end
76
+
77
+ # Resets but also clears configuration, loaded contracts, and plugins.
62
78
  def clear!
63
- Pacto.configuration.provider.reset!
79
+ Pacto::Resettable.reset_all
64
80
  @modes = nil
65
81
  @configuration = nil
66
82
  @registry = nil
67
- Pacto::ValidationRegistry.instance.reset!
68
83
  end
69
84
 
70
85
  def configure
@@ -75,29 +90,22 @@ module Pacto
75
90
  contract_registry.contracts_for(request_signature)
76
91
  end
77
92
 
93
+ # @throws Pacto::InvalidContract
78
94
  def validate_contract(contract)
79
95
  Pacto::MetaSchema.new.validate contract
80
- puts "Validating #{contract}"
81
96
  true
82
- rescue InvalidContract => exception
83
- puts 'Validation errors detected'
84
- exception.errors.each do |error|
85
- puts " Error: #{error}"
86
- end
87
- false
88
97
  end
89
98
 
90
- def load_contract(contract_path, host)
91
- load_contracts(contract_path, host).contracts.first
99
+ def load_contract(contract_path, host, format = :default)
100
+ load_contracts(contract_path, host, format).first
92
101
  end
93
102
 
94
- def load_contracts(contracts_path, host)
95
- files = ContractFiles.for(contracts_path)
96
- contracts = contract_factory.build(files, host)
103
+ def load_contracts(contracts_path, host, format = :default)
104
+ contracts = ContractFactory.load_contracts(contracts_path, host, format)
97
105
  contracts.each do |contract|
98
106
  contract_registry.register(contract)
99
107
  end
100
- ContractList.new(contracts)
108
+ ContractSet.new(contracts)
101
109
  end
102
110
  end
103
111
  end
@@ -0,0 +1,5 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ class Actor
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Actors
4
+ class FirstExampleSelector
5
+ def self.select(examples, _values)
6
+ Hashie::Mash.new examples.values.first
7
+ end
8
+ end
9
+ class RandomExampleSelector
10
+ def self.select(examples, _values)
11
+ Hashie::Mash.new examples.values.sample
12
+ end
13
+ end
14
+ class NamedExampleSelector
15
+ def self.select(examples, values)
16
+ name = values[:example_name]
17
+ if name.nil?
18
+ RandomExampleSelector.select(examples, values)
19
+ else
20
+ Hashie::Mash.new examples[name]
21
+ end
22
+ end
23
+ end
24
+ class FromExamples < Actor
25
+ def initialize(fallback_actor = JSONGenerator.new, selector = Pacto::Actors::FirstExampleSelector)
26
+ @fallback_actor = fallback_actor
27
+ @selector = selector
28
+ end
29
+
30
+ def build_request(contract, values = {})
31
+ request_values = (values || {}).dup
32
+ if contract.examples?
33
+ example = @selector.select(contract.examples, values)
34
+ data = contract.request.to_hash
35
+ request_values.merge! example_uri_values(contract)
36
+ data['uri'] = contract.request.uri(request_values)
37
+ data['body'] = example.request.body
38
+ data['method'] = contract.request.http_method
39
+ Pacto::PactoRequest.new(data)
40
+ else
41
+ @fallback_actor.build_request contract, values
42
+ end
43
+ end
44
+
45
+ def build_response(contract, values = {})
46
+ if contract.examples?
47
+ example = @selector.select(contract.examples, values)
48
+ data = contract.response.to_hash
49
+ data['body'] = example.response.body
50
+ Pacto::PactoResponse.new(data)
51
+ else
52
+ @fallback_actor.build_response contract, values
53
+ end
54
+ end
55
+
56
+ def example_uri_values(contract)
57
+ uri_template = contract.request.pattern.uri_template
58
+ if contract.examples && contract.examples.values.first[:request][:uri]
59
+ example_uri = contract.examples.values.first[:request][:uri]
60
+ uri_template.extract example_uri
61
+ else
62
+ {}
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Actors
4
+ class JSONGenerator < Actor
5
+ def build_request(contract, values = {})
6
+ data = contract.request.to_hash
7
+ data['uri'] = contract.request.uri(values)
8
+ data['body'] = JSON::Generator.generate(data['schema'])
9
+ data['method'] = contract.request.http_method
10
+ Pacto::PactoRequest.new(data)
11
+ end
12
+
13
+ def build_response(contract, _values = {})
14
+ data = contract.response.to_hash
15
+ data['body'] = JSON::Generator.generate(data['schema'])
16
+ Pacto::PactoResponse.new(data)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,75 @@
1
+ require 'thor'
2
+ require 'pacto'
3
+ require 'pacto/cli/helpers'
4
+
5
+ module Pacto
6
+ module CLI
7
+ class Main < Thor
8
+ include Pacto::CLI::Helpers
9
+
10
+ desc 'meta_validate [CONTRACTS...]', 'Validates a directory of contract definitions'
11
+ def meta_validate(*contracts)
12
+ invalid = []
13
+ each_contract(*contracts) do |contract_file|
14
+ begin
15
+ Pacto.validate_contract(contract_file)
16
+ say_status :validated, contract_file
17
+ rescue InvalidContract => exception
18
+ invalid << contract_file
19
+ shell.say_status :invalid, contract_file, :red
20
+ exception.errors.each do |error|
21
+ say set_color(" Error: #{error}", :red)
22
+ end
23
+ end
24
+ end
25
+ abort "The following contracts were invalid: #{invalid.join(',')}" unless invalid.empty?
26
+ say 'All contracts successfully meta-validated'
27
+ end
28
+
29
+ desc 'validate [CONTRACTS...]', 'Validates all contracts in a given directory against a given host'
30
+ method_option :host, type: :string, desc: 'Override host in contracts for validation'
31
+ def validate(*contracts)
32
+ host = options[:host]
33
+ WebMock.allow_net_connect!
34
+ banner = 'Validating contracts'
35
+ banner << " against host #{host}" unless host.nil?
36
+ say banner
37
+
38
+ invalid_contracts = []
39
+ tested_contracts = []
40
+ each_contract(*contracts) do |contract_file|
41
+ tested_contracts << contract_file
42
+ invalid_contracts << contract_file unless contract_is_valid?(contract_file, host)
43
+ end
44
+
45
+ validation_summary(tested_contracts, invalid_contracts)
46
+ end
47
+
48
+ private
49
+
50
+ def validation_summary(contracts, invalid_contracts)
51
+ if invalid_contracts.empty?
52
+ say set_color("#{contracts.size} valid contract#{contracts.size > 1 ? 's' : nil}", :green)
53
+ else
54
+ abort set_color("#{invalid_contracts.size} of #{contracts.size} failed. Check output for detailed error messages.", :red)
55
+ end
56
+ end
57
+
58
+ def contract_is_valid?(contract_file, host)
59
+ name = File.split(contract_file).last
60
+ contract = Pacto.load_contract(contract_file, host)
61
+ investigation = contract.simulate_request
62
+
63
+ if investigation.successful?
64
+ say_status 'OK!', name
65
+ true
66
+ else
67
+ say_status 'FAILED!', name, :red
68
+ say set_color(investigation.summary, :red)
69
+ say set_color(investigation.to_s, :red)
70
+ false
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,20 @@
1
+ module Pacto
2
+ module CLI
3
+ module Helpers
4
+ def each_contract(*contracts)
5
+ [*contracts].each do |contract|
6
+ if File.file? contract
7
+ yield contract
8
+ else # Should we assume it's a dir, or also support glob patterns?
9
+ contracts = Dir[File.join(contract, '**/*{.json.erb,.json}')]
10
+ fail Pacto::UI.colorize("No contracts found in directory #{contract}", :yellow) if contracts.empty?
11
+
12
+ contracts.sort.each do |contract_file|
13
+ yield contract_file
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,80 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ def self.consumers
4
+ @consumers ||= {}
5
+ end
6
+
7
+ def self.simulate_consumer(consumer_name = :consumer, &block)
8
+ consumers[consumer_name] ||= Consumer.new(consumer_name)
9
+ consumers[consumer_name].simulate(&block)
10
+ end
11
+
12
+ class Consumer
13
+ include Logger
14
+ include Resettable
15
+
16
+ def initialize(name = :consumer)
17
+ @name = name
18
+ end
19
+
20
+ def simulate(&block)
21
+ instance_eval(&block)
22
+ end
23
+
24
+ def playback(stenographer_script)
25
+ script = File.read(stenographer_script)
26
+ instance_eval script, stenographer_script
27
+ end
28
+
29
+ def self.reset!
30
+ Pacto.consumers.clear
31
+ end
32
+
33
+ def actor
34
+ @actor ||= Pacto::Actors::FromExamples.new
35
+ end
36
+
37
+ def actor=(actor)
38
+ fail ArgumentError, 'The actor must respond to :build_request' unless actor.respond_to? :build_request
39
+ @actor = actor
40
+ end
41
+
42
+ def request(contract, data = {})
43
+ contract = Pacto.contract_registry.find_by_name(contract) if contract.is_a? String
44
+ logger.info "Sending request to #{contract.name.inspect}"
45
+ logger.info " with #{data.inspect}"
46
+ reenact(contract, data)
47
+ rescue ContractNotFound => e
48
+ logger.warn "Ignoring request: #{e.message}"
49
+ end
50
+
51
+ def reenact(contract, data = {})
52
+ request = build_request contract, data
53
+ response = driver.execute request
54
+ [request, response]
55
+ end
56
+
57
+ # Returns the current driver
58
+ def driver
59
+ @driver ||= Pacto::Consumer::FaradayDriver.new
60
+ end
61
+
62
+ # Changes the driver
63
+ def driver=(driver)
64
+ fail ArgumentError, 'The driver must respond to :execute' unless driver.respond_to? :execute
65
+ @driver = driver
66
+ end
67
+
68
+ # @api private
69
+ def build_request(contract, data = {})
70
+ actor.build_request(contract, data[:values]).tap do |request|
71
+ if data[:headers] && data[:headers].respond_to?(:call)
72
+ request.headers = data[:headers].call(request.headers)
73
+ end
74
+ if data[:body] && data[:body].respond_to?(:call)
75
+ request.body = data[:body].call(request.body)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ class Consumer
4
+ class FaradayDriver
5
+ # Sends a Pacto::PactoRequest
6
+ def execute(req)
7
+ conn = Faraday.new(url: req.uri.site) do |faraday|
8
+ faraday.response :logger if Pacto.configuration.logger.level == :debug
9
+ faraday.adapter Faraday.default_adapter
10
+ end
11
+
12
+ response = conn.send(req.method) do |faraday_request|
13
+ faraday_request.url(req.uri.path, req.uri.query_values)
14
+ faraday_request.headers = req.headers
15
+ faraday_request.body = req.body
16
+ end
17
+
18
+ faraday_to_pacto_response response
19
+ end
20
+
21
+ private
22
+
23
+ # This belongs in an adapter
24
+ def faraday_to_pacto_response(faraday_response)
25
+ data = {
26
+ status: faraday_response.status,
27
+ headers: faraday_response.headers,
28
+ body: faraday_response.body
29
+ }
30
+ Pacto::PactoResponse.new(data)
31
+ end
32
+ end
33
+ end
34
+ end