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
@@ -1,66 +1,44 @@
1
- require 'json/schema_generator'
1
+ # -*- encoding : utf-8 -*-
2
+ require 'pacto/generator/native_contract_generator'
3
+ require 'pacto/generator/hint'
2
4
 
3
5
  module Pacto
4
- class Generator
5
- def initialize(schema_version = 'draft3',
6
- schema_generator = JSON::SchemaGenerator,
7
- validator = Pacto::MetaSchema.new,
8
- generator_options = Pacto.configuration.generator_options,
9
- filters = Pacto::Generator::Filters.new)
10
- @schema_version = schema_version
11
- @validator = validator
12
- @schema_generator = schema_generator
13
- @generator_options = generator_options
14
- @filters = filters
15
- end
6
+ module Generator
7
+ include Logger
16
8
 
17
- def generate(request_file, host)
18
- contract = Pacto.load_contract request_file, host
19
- request = contract.request
20
- response = contract.request.execute
21
- save(request_file, request, response)
22
- end
9
+ class << self
10
+ # Factory method to return the active contract generator implementation
11
+ def contract_generator
12
+ NativeContractGenerator.new
13
+ end
23
14
 
24
- def save(source, request, response)
25
- contract = generate_contract source, request, response
26
- pretty_contract = MultiJson.encode(contract, :pretty => true)
27
- # This is because of a discrepency w/ jruby vs MRI pretty json
28
- pretty_contract.gsub!(/^$\n/, '')
29
- @validator.validate pretty_contract
30
- pretty_contract
31
- end
15
+ # Factory method to return the active contract generator implementation
16
+ def schema_generator
17
+ JSON::SchemaGenerator
18
+ end
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
32
23
 
33
- private
24
+ def configure
25
+ yield(configuration)
26
+ end
34
27
 
35
- def generate_contract(source, request, response)
36
- {
37
- :request => generate_request(request, response, source),
38
- :response => generate_response(request, response, source)
39
- }
28
+ def hint_for(pacto_request)
29
+ configuration.hints.find { |hint| hint.matches? pacto_request }
30
+ end
40
31
  end
41
32
 
42
- def generate_request(request, response, source)
43
- {
44
- :headers => @filters.filter_request_headers(request, response),
45
- :method => request.method,
46
- :params => request.params,
47
- :path => request.path,
48
- :body => generate_body(source, request.body)
49
- }.delete_if { |k, v| v.nil? }
50
- end
33
+ class Configuration
34
+ attr_reader :hints
51
35
 
52
- def generate_response(request, response, source)
53
- {
54
- :headers => @filters.filter_response_headers(request, response),
55
- :status => response.status,
56
- :body => generate_body(source, response.body)
57
- }.delete_if { |k, v| v.nil? }
58
- end
36
+ def initialize
37
+ @hints = Set.new
38
+ end
59
39
 
60
- def generate_body(source, body)
61
- if body && !body.empty?
62
- body_schema = JSON::SchemaGenerator.generate source, body, @generator_options
63
- MultiJson.load(body_schema)
40
+ def hint(name, hint_data)
41
+ @hints << Pacto::Generator::Hint.new(hint_data.merge(service_name: name))
64
42
  end
65
43
  end
66
44
  end
@@ -1,26 +1,27 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Pacto
2
- class Generator
3
+ module Generator
3
4
  class Filters
4
- CONNECTION_CONTROL_HEADERS = %w{
5
+ CONNECTION_CONTROL_HEADERS = %w(
5
6
  Via
6
7
  Server
7
8
  Connection
8
9
  Transfer-Encoding
9
10
  Content-Length
10
- }
11
+ )
11
12
 
12
13
  FRESHNESS_HEADERS =
13
- %w{
14
+ %w(
14
15
  Date
15
16
  Last-Modified
16
17
  ETag
17
- }
18
+ )
18
19
 
19
20
  HEADERS_TO_FILTER = CONNECTION_CONTROL_HEADERS + FRESHNESS_HEADERS
20
21
 
21
22
  def filter_request_headers(request, response)
22
23
  # FIXME: Do we need to handle all these cases in real situations, or just because of stubbing?
23
- vary_headers = response.headers['Vary'] || []
24
+ vary_headers = response.headers['vary'] || response.headers['Vary'] || []
24
25
  vary_headers = [vary_headers] if vary_headers.is_a? String
25
26
  vary_headers = vary_headers.map do |h|
26
27
  h.split(',').map(&:strip)
@@ -31,7 +32,7 @@ module Pacto
31
32
  end
32
33
  end
33
34
 
34
- def filter_response_headers(request, response)
35
+ def filter_response_headers(_request, response)
35
36
  Pacto::Extensions.normalize_header_keys(response.headers).reject do |header|
36
37
  (HEADERS_TO_FILTER.include? header) || (header.start_with?('X-'))
37
38
  end
@@ -0,0 +1,26 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Generator
4
+ class Hint < Pacto::RequestClause
5
+ property :service_name, required: true
6
+ property :target_file
7
+
8
+ def initialize(data)
9
+ super
10
+ self.target_file ||= "#{slugify(service_name)}.json"
11
+ self
12
+ end
13
+
14
+ def matches?(pacto_request)
15
+ return false if pacto_request.nil?
16
+ Pacto::RequestPattern.for(self).matches?(pacto_request)
17
+ end
18
+
19
+ private
20
+
21
+ def slugify(path)
22
+ path.downcase.gsub(' ', '_')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,74 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'json/schema_generator'
3
+ require 'pacto/contract_builder'
4
+
5
+ module Pacto
6
+ module Generator
7
+ class NativeContractGenerator
8
+ include Logger
9
+
10
+ def initialize(_schema_version = 'draft3',
11
+ schema_generator = JSON::SchemaGenerator,
12
+ validator = Pacto::MetaSchema.new,
13
+ filters = Pacto::Generator::Filters.new,
14
+ consumer = Pacto::Consumer.new)
15
+ @contract_builder = ContractBuilder.new(schema_generator: schema_generator, filters: filters)
16
+ @consumer = consumer
17
+ @validator = validator
18
+ end
19
+
20
+ def generate(pacto_request, pacto_response)
21
+ return unless Pacto.generating?
22
+ logger.debug("Generating Contract for #{pacto_request}, #{pacto_response}")
23
+ begin
24
+ contract_file = load_contract_file(pacto_request)
25
+
26
+ unless File.exist? contract_file
27
+ uri = URI(pacto_request.uri)
28
+ FileUtils.mkdir_p(File.dirname contract_file)
29
+ raw_contract = save(uri, pacto_request, pacto_response)
30
+ File.write(contract_file, raw_contract)
31
+ logger.debug("Generating #{contract_file}")
32
+
33
+ Pacto.load_contract contract_file, uri.host
34
+ end
35
+ rescue => e
36
+ raise StandardError, "Error while generating Contract #{contract_file}: #{e.message}", e.backtrace
37
+ end
38
+ end
39
+
40
+ def generate_from_partial_contract(request_file, host)
41
+ contract = Pacto.load_contract request_file, host
42
+ request, response = @consumer.request(contract)
43
+ save(request_file, request, response)
44
+ end
45
+
46
+ def save(source, request, response)
47
+ @contract_builder.source = source
48
+ # TODO: Get rid of the generate_contract call, just use add_example/infer_all
49
+ @contract_builder.add_example('default', request, response).generate_contract(request, response) # .infer_all
50
+ @contract_builder.without_examples if Pacto.configuration.generator_options[:no_examples]
51
+ contract = @contract_builder.build_hash
52
+ pretty_contract = MultiJson.encode(contract, pretty: true)
53
+ # This is because of a discrepency w/ jruby vs MRI pretty json
54
+ pretty_contract.gsub!(/^$\n/, '')
55
+ @validator.validate pretty_contract
56
+ pretty_contract
57
+ end
58
+
59
+ private
60
+
61
+ def load_contract_file(pacto_request)
62
+ hint = Pacto::Generator.hint_for(pacto_request)
63
+ if hint.nil?
64
+ uri = URI(pacto_request.uri)
65
+ path = uri.path
66
+ basename = File.basename(path, '.json') + '.json'
67
+ File.join(Pacto.configuration.contracts_path, uri.host, File.dirname(path), basename)
68
+ else
69
+ File.expand_path(hint.target_file, Pacto.configuration.contracts_path)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require_relative '../erb_processor'
2
3
 
3
4
  module Pacto
@@ -9,7 +10,7 @@ module Pacto
9
10
 
10
11
  def process(contracts, request_signature, response)
11
12
  bound_values = contracts.empty? ? {} : contracts.first.values
12
- bound_values.merge!(:req => { 'HEADERS' => request_signature.headers})
13
+ bound_values.merge!(req: { 'HEADERS' => request_signature.headers })
13
14
  response.body = @processor.process response.body, bound_values
14
15
  response.body
15
16
  end
@@ -0,0 +1,49 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ class Investigation
4
+ include Logger
5
+ attr_reader :request, :response, :contract, :citations
6
+
7
+ def initialize(request, response, contract = nil, citations = nil)
8
+ @request = request
9
+ @response = response
10
+ @contract = contract
11
+ @citations = citations || []
12
+ end
13
+
14
+ def successful?
15
+ @citations.empty?
16
+ end
17
+
18
+ def against_contract?(contract_pattern)
19
+ return nil if @contract.nil?
20
+
21
+ case contract_pattern
22
+ when String
23
+ @contract if @contract.file.eql? contract_pattern
24
+ when Regexp
25
+ @contract if @contract.file =~ contract_pattern
26
+ end
27
+ end
28
+
29
+ def to_s
30
+ contract_name = @contract.nil? ? 'nil' : contract.name
31
+ citation_string = Pacto::UI.colorize(@citations.join("\n\t\t"), :red)
32
+ ''"
33
+ Investigation:
34
+ \tContract: #{contract_name}
35
+ \tRequest: #{@request}
36
+ \tCitations: \n\t\t#{citation_string}
37
+ "''
38
+ end
39
+
40
+ def summary
41
+ if @contract.nil?
42
+ "Missing contract for services provided by #{@request.uri.host}"
43
+ else
44
+ status = successful? ? 'successful' : 'unsuccessful'
45
+ "#{status} investigation of #{@contract.name}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require 'forwardable'
2
3
 
3
4
  module Pacto
@@ -1,19 +1,25 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  module Pacto
2
3
  class MetaSchema
3
4
  attr_accessor :schema, :engine
4
5
 
5
6
  def initialize(engine = JSON::Validator)
6
7
  @schema = File.join(File.dirname(File.expand_path(__FILE__)), '../../resources/contract_schema.json')
7
- @base_schema = File.join(File.dirname(File.expand_path(__FILE__)), '../../resources/draft-03.json')
8
- JSON::Validator.validate!(@base_schema, @schema)
8
+ base_schemas = ['../../resources/draft-03.json', '../../resources/draft-04.json']
9
+ validatable = false
10
+ base_schemas.each do |base_schema|
11
+ base_schema_file = File.join(File.dirname(File.expand_path(__FILE__)), base_schema)
12
+ # This has a side-effect of caching local schemas, so we don't
13
+ # look up json-schemas over HTTP.
14
+ validatable ||= JSON::Validator.validate(base_schema_file, @schema)
15
+ end
16
+ fail 'Could not validate metaschema against any known version of json-schema' unless validatable
9
17
  @engine = engine
10
18
  end
11
19
 
12
20
  def validate(definition)
13
- errors = engine.fully_validate(schema, definition, :version => :draft3)
14
- unless errors.empty?
15
- fail InvalidContract, errors
16
- end
21
+ errors = engine.fully_validate(schema, definition)
22
+ fail InvalidContract, errors unless errors.empty?
17
23
  end
18
24
  end
19
25
  end
@@ -0,0 +1,60 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ # Builds {Pacto::Contract} instances from Pacto's native Contract format.
4
+ class NativeContractFactory
5
+ attr_reader :schema
6
+
7
+ def initialize(options = {})
8
+ @schema = options[:schema] || MetaSchema.new
9
+ end
10
+
11
+ def build_from_file(contract_path, host)
12
+ contract_definition = File.read(contract_path)
13
+ definition = JSON.parse(contract_definition)
14
+ schema.validate definition
15
+ definition['request'].merge!('host' => host)
16
+ body_to_schema(definition, 'request', contract_path)
17
+ body_to_schema(definition, 'response', contract_path)
18
+ method_to_http_method(definition, contract_path)
19
+ request = RequestClause.new(definition['request'])
20
+ response = ResponseClause.new(definition['response'])
21
+ Contract.new(request: request, response: response, file: contract_path, name: definition['name'], examples: definition['examples'])
22
+ end
23
+
24
+ def files_for(contracts_dir)
25
+ full_path = Pathname.new(contracts_dir).realpath
26
+
27
+ if full_path.directory?
28
+ all_json_files = "#{full_path}/**/*.json"
29
+ Dir.glob(all_json_files).map do |f|
30
+ Pathname.new(f)
31
+ end
32
+ else
33
+ [full_path]
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def body_to_schema(definition, section, file)
40
+ schema = definition[section].delete 'body'
41
+ return nil unless schema
42
+
43
+ Pacto::UI.deprecation "Contract format deprecation: #{section}:body will be moved to #{section}:schema (#{file})"
44
+ definition[section]['schema'] = schema
45
+ end
46
+
47
+ def method_to_http_method(definition, file)
48
+ method = definition['request'].delete 'method'
49
+ return nil unless method
50
+
51
+ Pacto::UI.deprecation "Contract format deprecation: request:method will be moved to request:http_method (#{file})"
52
+ definition['request']['http_method'] = method
53
+ end
54
+ end
55
+ end
56
+
57
+ factory = Pacto::NativeContractFactory.new
58
+
59
+ Pacto::ContractFactory.add_factory(:native, factory)
60
+ Pacto::ContractFactory.add_factory(:default, factory)
@@ -0,0 +1,42 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Pacto
3
+ module Observers
4
+ class Stenographer
5
+ def initialize(output)
6
+ @output = output
7
+ end
8
+
9
+ def log_investigation(investigation)
10
+ return if @output.nil?
11
+
12
+ contract = investigation.contract
13
+ request = investigation.request
14
+ response = investigation.response
15
+ name = name_for(contract, request)
16
+ values = values_for(contract, request)
17
+
18
+ msg = "request #{name.inspect}, values: #{values.inspect}, response: {status: #{response.status}} # #{number_of_citations(investigation)} contract violations"
19
+ @output.puts msg
20
+ @output.flush
21
+ end
22
+
23
+ protected
24
+
25
+ def name_for(contract, request)
26
+ return "Unknown (#{request.uri})" if contract.nil?
27
+ contract.name
28
+ end
29
+
30
+ def number_of_citations(investigation)
31
+ return 0 if investigation.nil?
32
+ return 0 if investigation.citations.nil?
33
+ investigation.citations.size.to_s
34
+ end
35
+
36
+ def values_for(_contract, request)
37
+ # FIXME: Extract vars w/ URI::Template
38
+ request.uri.query_values
39
+ end
40
+ end
41
+ end
42
+ end