pacto 0.3.0.pre → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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