pacto 0.3.0.pre → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rubocop-todo.yml +0 -27
  4. data/.rubocop.yml +9 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +4 -5
  8. data/CONTRIBUTING.md +112 -0
  9. data/Gemfile +5 -0
  10. data/Guardfile +18 -13
  11. data/README.md +157 -101
  12. data/Rakefile +3 -3
  13. data/features/configuration/strict_matchers.feature +97 -0
  14. data/features/evolve/README.md +11 -0
  15. data/features/evolve/existing_services.feature +82 -0
  16. data/features/generate/README.md +5 -0
  17. data/features/generate/generation.feature +28 -0
  18. data/features/steps/pacto_steps.rb +75 -0
  19. data/features/stub/README.md +2 -0
  20. data/features/stub/templates.feature +46 -0
  21. data/features/support/env.rb +11 -5
  22. data/features/validate/README.md +1 -0
  23. data/features/validate/body_only.feature +85 -0
  24. data/features/{journeys/validation.feature → validate/meta_validation.feature} +41 -24
  25. data/features/validate/validation.feature +36 -0
  26. data/lib/pacto.rb +61 -33
  27. data/lib/pacto/contract.rb +18 -15
  28. data/lib/pacto/contract_factory.rb +14 -11
  29. data/lib/pacto/contract_files.rb +17 -0
  30. data/lib/pacto/contract_list.rb +17 -0
  31. data/lib/pacto/contract_validator.rb +29 -0
  32. data/lib/pacto/core/configuration.rb +19 -17
  33. data/lib/pacto/core/contract_registry.rb +43 -0
  34. data/lib/pacto/core/{callback.rb → hook.rb} +3 -3
  35. data/lib/pacto/core/modes.rb +33 -0
  36. data/lib/pacto/core/validation_registry.rb +45 -0
  37. data/lib/pacto/erb_processor.rb +0 -1
  38. data/lib/pacto/extensions.rb +18 -4
  39. data/lib/pacto/generator.rb +34 -49
  40. data/lib/pacto/generator/filters.rb +41 -0
  41. data/lib/pacto/hooks/erb_hook.rb +4 -3
  42. data/lib/pacto/logger.rb +4 -2
  43. data/lib/pacto/meta_schema.rb +4 -2
  44. data/lib/pacto/rake_task.rb +28 -25
  45. data/lib/pacto/request_clause.rb +43 -0
  46. data/lib/pacto/request_pattern.rb +8 -0
  47. data/lib/pacto/response_clause.rb +15 -0
  48. data/lib/pacto/rspec.rb +102 -0
  49. data/lib/pacto/stubs/uri_pattern.rb +23 -0
  50. data/lib/pacto/stubs/webmock_adapter.rb +69 -0
  51. data/lib/pacto/stubs/webmock_helper.rb +71 -0
  52. data/lib/pacto/ui.rb +7 -0
  53. data/lib/pacto/uri.rb +9 -0
  54. data/lib/pacto/validation.rb +57 -0
  55. data/lib/pacto/validators/body_validator.rb +41 -0
  56. data/lib/pacto/validators/request_body_validator.rb +23 -0
  57. data/lib/pacto/validators/response_body_validator.rb +23 -0
  58. data/lib/pacto/validators/response_header_validator.rb +49 -0
  59. data/lib/pacto/validators/response_status_validator.rb +24 -0
  60. data/lib/pacto/version.rb +1 -1
  61. data/pacto.gemspec +33 -29
  62. data/resources/contract_schema.json +8 -176
  63. data/resources/draft-03.json +174 -0
  64. data/spec/integration/data/strict_contract.json +2 -2
  65. data/spec/integration/e2e_spec.rb +22 -31
  66. data/spec/integration/rspec_spec.rb +94 -0
  67. data/spec/integration/templating_spec.rb +9 -12
  68. data/{lib → spec}/pacto/server.rb +0 -0
  69. data/{lib → spec}/pacto/server/dummy.rb +11 -8
  70. data/{lib → spec}/pacto/server/playback_servlet.rb +1 -1
  71. data/spec/spec_helper.rb +2 -0
  72. data/spec/unit/hooks/erb_hook_spec.rb +15 -15
  73. data/spec/unit/pacto/configuration_spec.rb +2 -10
  74. data/spec/unit/pacto/contract_factory_spec.rb +16 -13
  75. data/spec/unit/pacto/contract_files_spec.rb +42 -0
  76. data/spec/unit/pacto/contract_list_spec.rb +35 -0
  77. data/spec/unit/pacto/contract_spec.rb +43 -44
  78. data/spec/unit/pacto/contract_validator_spec.rb +85 -0
  79. data/spec/unit/pacto/core/configuration_spec.rb +4 -11
  80. data/spec/unit/pacto/core/contract_registry_spec.rb +119 -0
  81. data/spec/unit/pacto/core/modes_spec.rb +18 -0
  82. data/spec/unit/pacto/core/validation_registry_spec.rb +76 -0
  83. data/spec/unit/pacto/core/validation_spec.rb +60 -0
  84. data/spec/unit/pacto/extensions_spec.rb +14 -23
  85. data/spec/unit/pacto/generator/filters_spec.rb +99 -0
  86. data/spec/unit/pacto/generator_spec.rb +34 -73
  87. data/spec/unit/pacto/meta_schema_spec.rb +46 -6
  88. data/spec/unit/pacto/pacto_spec.rb +17 -15
  89. data/spec/unit/pacto/{request_spec.rb → request_clause_spec.rb} +32 -44
  90. data/spec/unit/pacto/request_pattern_spec.rb +22 -0
  91. data/spec/unit/pacto/response_clause_spec.rb +54 -0
  92. data/spec/unit/pacto/stubs/uri_pattern_spec.rb +28 -0
  93. data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +205 -0
  94. data/spec/unit/pacto/stubs/webmock_helper_spec.rb +20 -0
  95. data/spec/unit/pacto/uri_spec.rb +20 -0
  96. data/spec/unit/pacto/validators/body_validator_spec.rb +105 -0
  97. data/spec/unit/pacto/validators/response_header_validator_spec.rb +94 -0
  98. data/spec/unit/pacto/validators/response_status_validator_spec.rb +20 -0
  99. metadata +230 -146
  100. data/features/generation/generation.feature +0 -25
  101. data/lib/pacto/core/contract_repository.rb +0 -44
  102. data/lib/pacto/hash_merge_processor.rb +0 -14
  103. data/lib/pacto/request.rb +0 -57
  104. data/lib/pacto/response.rb +0 -63
  105. data/lib/pacto/response_adapter.rb +0 -24
  106. data/lib/pacto/stubs/built_in.rb +0 -57
  107. data/spec/unit/pacto/core/contract_repository_spec.rb +0 -133
  108. data/spec/unit/pacto/hash_merge_processor_spec.rb +0 -20
  109. data/spec/unit/pacto/response_adapter_spec.rb +0 -25
  110. data/spec/unit/pacto/response_spec.rb +0 -201
  111. data/spec/unit/pacto/stubs/built_in_spec.rb +0 -168
@@ -1,37 +1,54 @@
1
- Feature: Validation journey
2
- Scenario: Meta-validation of a valid contract
1
+ Feature: Meta-validation
2
+
3
+ Meta-validation ensures that a Contract file matches the Contract format. It does not validation actual responses, just the Contract itself.
4
+
5
+ You can easily do meta-validation with the Rake task pacto:meta_validate, or programmatically.
6
+
7
+ Background:
3
8
  Given a file named "contracts/my_contract.json" with:
4
- """
5
- {
6
- "request": {
7
- "method": "GET",
8
- "path": "/hello_world",
9
- "headers": {
10
- "Accept": "application/json"
9
+ """
10
+ {
11
+ "request": {
12
+ "method": "GET",
13
+ "path": "/hello_world",
14
+ "headers": {
15
+ "Accept": "application/json"
16
+ },
17
+ "params": {}
11
18
  },
12
- "params": {}
13
- },
14
19
 
15
- "response": {
16
- "status": 200,
17
- "headers": {
18
- "Content-Type": "application/json"
19
- },
20
- "body": {
21
- "description": "A simple response",
22
- "type": "object",
23
- "properties": {
24
- "message": {
25
- "type": "string"
20
+ "response": {
21
+ "status": 200,
22
+ "headers": {
23
+ "Content-Type": "application/json"
24
+ },
25
+ "body": {
26
+ "description": "A simple response",
27
+ "type": "object",
28
+ "properties": {
29
+ "message": {
30
+ "type": "string"
31
+ }
26
32
  }
27
33
  }
28
34
  }
29
35
  }
30
- }
31
- """
36
+ """
37
+
38
+ Scenario: Meta-validation via a rake task
32
39
  When I successfully run `bundle exec rake pacto:meta_validate['tmp/aruba/contracts/my_contract.json']`
33
40
  Then the output should contain "All contracts successfully meta-validated"
34
41
 
42
+ Scenario: Programmatic meta-validation
43
+ Given a file named "meta_validate.rb" with:
44
+ """ruby
45
+ require 'pacto'
46
+ Pacto.validate_contract 'contracts/my_contract.json'
47
+ """
48
+ When I successfully run `bundle exec ruby meta_validate.rb`
49
+ Then the output should contain "Validating contracts/my_contract.json"
50
+
51
+ # The tests from here down should probably be specs instead of relish
35
52
 
36
53
  Scenario: Meta-validation of an invalid contract
37
54
  Given a file named "contracts/my_contract.json" with:
@@ -0,0 +1,36 @@
1
+ Feature: Validation
2
+
3
+ Validation ensures that a external service conform to a Contract.
4
+
5
+ Scenario: Validation via a rake task
6
+ Given a file named "contracts/simple_contract.json" with:
7
+ """
8
+ {
9
+ "request": {
10
+ "method": "GET",
11
+ "path": "/hello",
12
+ "headers": { "Accept": "application/json" },
13
+ "params": {}
14
+ },
15
+
16
+ "response": {
17
+ "status": 200,
18
+ "headers": { "Content-Type": "application/json" },
19
+ "body": {
20
+ "description": "A simple response",
21
+ "type": "object",
22
+ "properties": {
23
+ "message": { "type": "string" }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ """
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:
31
+ """"
32
+ Validating contracts in directory tmp/aruba/contracts/simple_contract.json against host http://localhost:8000
33
+
34
+ simple_contract.json: OK!
35
+ 1 valid contract
36
+ """
@@ -1,6 +1,8 @@
1
1
  require 'pacto/version'
2
2
 
3
- require 'httparty'
3
+ require 'addressable/template'
4
+ require 'middleware'
5
+ require 'faraday'
4
6
  require 'hash_deep_merge'
5
7
  require 'multi_json'
6
8
  require 'json-schema'
@@ -10,66 +12,92 @@ require 'ostruct'
10
12
  require 'erb'
11
13
  require 'logger'
12
14
 
13
- require 'pacto/core/contract_repository'
14
- require 'pacto/core/configuration'
15
- require 'pacto/core/callback'
16
15
  require 'pacto/logger'
16
+ require 'pacto/ui'
17
+ require 'pacto/core/contract_registry'
18
+ require 'pacto/core/validation_registry'
19
+ require 'pacto/core/configuration'
20
+ require 'pacto/core/modes'
21
+ require 'pacto/core/hook'
17
22
  require 'pacto/exceptions/invalid_contract.rb'
18
23
  require 'pacto/extensions'
19
- require 'pacto/request'
20
- require 'pacto/response_adapter'
21
- require 'pacto/response'
22
- require 'pacto/stubs/built_in'
24
+ require 'pacto/request_clause'
25
+ require 'pacto/response_clause'
26
+ require 'pacto/stubs/webmock_adapter'
27
+ require 'pacto/stubs/uri_pattern'
23
28
  require 'pacto/contract'
29
+ require 'pacto/contract_validator'
24
30
  require 'pacto/contract_factory'
25
- require 'pacto/erb_processor'
26
- require 'pacto/hash_merge_processor'
27
- require 'pacto/stubs/built_in'
31
+ require 'pacto/validation'
28
32
  require 'pacto/meta_schema'
29
33
  require 'pacto/hooks/erb_hook'
30
34
  require 'pacto/generator'
35
+ require 'pacto/generator/filters'
36
+ require 'pacto/contract_files'
37
+ require 'pacto/contract_list'
38
+ require 'pacto/request_pattern'
39
+ require 'pacto/uri'
40
+
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'
31
47
 
32
48
  module Pacto
33
49
  class << self
50
+ def contract_factory
51
+ @factory = ContractFactory.new
52
+ end
34
53
 
35
54
  def configuration
36
55
  @configuration ||= Configuration.new
37
56
  end
38
57
 
58
+ def contract_registry
59
+ @registry ||= ContractRegistry.new
60
+ end
61
+
39
62
  def clear!
40
63
  Pacto.configuration.provider.reset!
64
+ @modes = nil
41
65
  @configuration = nil
42
- unregister_all!
66
+ @registry = nil
67
+ Pacto::ValidationRegistry.instance.reset!
43
68
  end
44
69
 
45
70
  def configure
46
71
  yield(configuration)
47
72
  end
48
- end
49
73
 
50
- def self.validate_contract contract
51
- Pacto::MetaSchema.new.validate contract
52
- puts 'All contracts successfully meta-validated'
53
- true
54
- rescue InvalidContract => exception
55
- puts 'Validation errors detected'
56
- exception.errors.each do |error|
57
- puts " Error: #{error}"
74
+ def contracts_for(request_signature)
75
+ contract_registry.contracts_for(request_signature)
58
76
  end
59
- false
60
- end
61
-
62
- def self.build_from_file(contract_path, host, file_pre_processor = Pacto.configuration.preprocessor)
63
- ContractFactory.build_from_file(contract_path, host, file_pre_processor)
64
- end
65
77
 
66
- def self.load(contract_name)
67
- build_from_file(path_for(contract_name), nil)
68
- end
78
+ def validate_contract(contract)
79
+ Pacto::MetaSchema.new.validate contract
80
+ puts "Validating #{contract}"
81
+ 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
+ end
69
89
 
70
- private
90
+ def load_contract(contract_path, host)
91
+ load_contracts(contract_path, host).contracts.first
92
+ end
71
93
 
72
- def self.path_for(contract)
73
- File.join(configuration.contracts_path, "#{contract}.json")
94
+ def load_contracts(contracts_path, host)
95
+ files = ContractFiles.for(contracts_path)
96
+ contracts = contract_factory.build(files, host)
97
+ contracts.each do |contract|
98
+ contract_registry.register(contract)
99
+ end
100
+ ContractList.new(contracts)
101
+ end
74
102
  end
75
103
  end
@@ -1,25 +1,33 @@
1
1
  module Pacto
2
2
  class Contract
3
- attr_reader :values
4
- attr_reader :request, :response
3
+ attr_reader :name, :values, :request, :response, :file, :request_pattern
5
4
 
6
- def initialize(request, response, file = nil)
5
+ def initialize(request, response, file, name = nil, request_pattern_provider = RequestPattern)
7
6
  @request = request
8
7
  @response = response
9
- @file = file
8
+ @file = file.to_s
9
+ @name = name || @file
10
+ @request_pattern = request_pattern_provider.for(request)
11
+ @values = {}
10
12
  end
11
13
 
12
- def stub_contract! values = {}
14
+ def stub_contract!(values = {})
13
15
  @values = values
14
- @stub = Pacto.configuration.provider.stub_request!(@request, stub_response) unless @request.nil?
16
+ Pacto.configuration.provider.stub_request!(request, response)
15
17
  end
16
18
 
17
- def validate(response_gotten = provider_response, opt = {})
18
- @response.validate(response_gotten, opt)
19
+ def validate_provider(opts = {})
20
+ request = @request
21
+ response = provider_response
22
+ validate_consumer request, response, opts
19
23
  end
20
24
 
21
- def matches? request_signature
22
- @stub.matches? request_signature unless @stub.nil?
25
+ def validate_consumer(request, response, opts = {})
26
+ Pacto::ContractValidator.validate self, request, response, opts
27
+ end
28
+
29
+ def matches?(request_signature)
30
+ request_pattern.matches? request_signature
23
31
  end
24
32
 
25
33
  private
@@ -27,10 +35,5 @@ module Pacto
27
35
  def provider_response
28
36
  @request.execute
29
37
  end
30
-
31
- def stub_response
32
- @response.instantiate
33
- end
34
-
35
38
  end
36
39
  end
@@ -1,19 +1,22 @@
1
1
  module Pacto
2
2
  class ContractFactory
3
- def self.build_from_file(contract_path, host, preprocessor)
3
+ attr_reader :schema
4
+
5
+ def initialize(options = {})
6
+ @schema = options[:schema] || MetaSchema.new
7
+ end
8
+
9
+ def build(contract_files, host)
10
+ contract_files.map { |file| build_from_file(file, host) }
11
+ end
12
+
13
+ def build_from_file(contract_path, host)
4
14
  contract_definition = File.read(contract_path)
5
- if preprocessor
6
- contract_definition = preprocessor.process(contract_definition)
7
- end
8
15
  definition = JSON.parse(contract_definition)
9
16
  schema.validate definition
10
- request = Request.new(host, definition['request'])
11
- response = Response.new(definition['response'])
12
- Contract.new(request, response, contract_path)
13
- end
14
-
15
- def self.schema
16
- @schema ||= MetaSchema.new
17
+ request = RequestClause.new(host, definition['request'])
18
+ response = ResponseClause.new(definition['response'])
19
+ Contract.new(request, response, contract_path, definition['name'])
17
20
  end
18
21
  end
19
22
  end
@@ -0,0 +1,17 @@
1
+ require 'pathname'
2
+ module Pacto
3
+ class ContractFiles
4
+ def self.for(path)
5
+ full_path = Pathname.new(path).realpath
6
+
7
+ if full_path.directory?
8
+ all_json_files = "#{full_path}/**/*.json"
9
+ Dir.glob(all_json_files).map do |f|
10
+ Pathname.new(f)
11
+ end
12
+ else
13
+ [full_path]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Pacto
2
+ class ContractList
3
+ attr_reader :contracts
4
+
5
+ def initialize(contracts)
6
+ @contracts = contracts
7
+ end
8
+
9
+ def stub_all(values = {})
10
+ contracts.each { |contract| contract.stub_contract!(values) }
11
+ end
12
+
13
+ def validate_all
14
+ contracts.map { |contract| contract.validate_provider }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Pacto
2
+ class ContractValidator
3
+ class << self
4
+ def validate(contract, request, response, opts)
5
+ env = {
6
+ :contract => contract,
7
+ :actual_request => request,
8
+ :actual_response => response,
9
+ :validation_results => []
10
+ }
11
+ validation_stack(opts).call env
12
+ env[:validation_results].compact
13
+ end
14
+
15
+ private
16
+
17
+ def validation_stack(opts)
18
+ Middleware::Builder.new do
19
+ use Pacto::Validators::RequestBodyValidator
20
+ unless opts[:body_only]
21
+ use Pacto::Validators::ResponseStatusValidator
22
+ use Pacto::Validators::ResponseHeaderValidator
23
+ end
24
+ use Pacto::Validators::ResponseBodyValidator
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,33 +1,35 @@
1
1
  module Pacto
2
2
  class Configuration
3
- attr_accessor :preprocessor, :postprocessor, :provider, :strict_matchers, :contracts_path, :logger
4
- attr_reader :callback
3
+ attr_accessor :provider, :strict_matchers,
4
+ :contracts_path, :logger, :generator_options
5
+ attr_reader :hook
5
6
 
6
7
  def initialize
7
- @preprocessor = ERBProcessor.new
8
- @postprocessor = HashMergeProcessor.new
9
- @provider = Pacto::Stubs::BuiltIn.new
8
+ @provider = Stubs::WebMockAdapter.new
10
9
  @strict_matchers = true
11
10
  @contracts_path = nil
12
11
  @logger = Logger.instance
13
- if ENV['PACTO_DEBUG']
14
- @logger.level = :debug
12
+ define_logger_level
13
+ @hook = Hook.new {}
14
+ @generator_options = { :schema_version => 'draft3' }
15
+ end
16
+
17
+ def register_hook(hook = nil, &block)
18
+ if block_given?
19
+ @hook = Hook.new(&block)
15
20
  else
16
- @logger.level = :default
21
+ fail 'Expected a Pacto::Hook' unless hook.is_a? Hook
22
+ @hook = hook
17
23
  end
18
- @callback = Pacto::Hooks::ERBHook.new
19
24
  end
20
25
 
21
- def register_contract(contract = nil, *tags)
22
- Pacto.register_contract(contract, *tags)
23
- end
26
+ private
24
27
 
25
- def register_callback(callback = nil, &block)
26
- if block_given?
27
- @callback = Pacto::Callback.new(&block)
28
+ def define_logger_level
29
+ if ENV['PACTO_DEBUG']
30
+ @logger.level = :debug
28
31
  else
29
- raise 'Expected a Pacto::Callback' unless callback.is_a? Pacto::Callback
30
- @callback = callback
32
+ @logger.level = :default
31
33
  end
32
34
  end
33
35
  end