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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rubocop-todo.yml +0 -27
- data/.rubocop.yml +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -5
- data/CONTRIBUTING.md +112 -0
- data/Gemfile +5 -0
- data/Guardfile +18 -13
- data/README.md +157 -101
- data/Rakefile +3 -3
- data/features/configuration/strict_matchers.feature +97 -0
- data/features/evolve/README.md +11 -0
- data/features/evolve/existing_services.feature +82 -0
- data/features/generate/README.md +5 -0
- data/features/generate/generation.feature +28 -0
- data/features/steps/pacto_steps.rb +75 -0
- data/features/stub/README.md +2 -0
- data/features/stub/templates.feature +46 -0
- data/features/support/env.rb +11 -5
- data/features/validate/README.md +1 -0
- data/features/validate/body_only.feature +85 -0
- data/features/{journeys/validation.feature → validate/meta_validation.feature} +41 -24
- data/features/validate/validation.feature +36 -0
- data/lib/pacto.rb +61 -33
- data/lib/pacto/contract.rb +18 -15
- data/lib/pacto/contract_factory.rb +14 -11
- data/lib/pacto/contract_files.rb +17 -0
- data/lib/pacto/contract_list.rb +17 -0
- data/lib/pacto/contract_validator.rb +29 -0
- data/lib/pacto/core/configuration.rb +19 -17
- data/lib/pacto/core/contract_registry.rb +43 -0
- data/lib/pacto/core/{callback.rb → hook.rb} +3 -3
- data/lib/pacto/core/modes.rb +33 -0
- data/lib/pacto/core/validation_registry.rb +45 -0
- data/lib/pacto/erb_processor.rb +0 -1
- data/lib/pacto/extensions.rb +18 -4
- data/lib/pacto/generator.rb +34 -49
- data/lib/pacto/generator/filters.rb +41 -0
- data/lib/pacto/hooks/erb_hook.rb +4 -3
- data/lib/pacto/logger.rb +4 -2
- data/lib/pacto/meta_schema.rb +4 -2
- data/lib/pacto/rake_task.rb +28 -25
- data/lib/pacto/request_clause.rb +43 -0
- data/lib/pacto/request_pattern.rb +8 -0
- data/lib/pacto/response_clause.rb +15 -0
- data/lib/pacto/rspec.rb +102 -0
- data/lib/pacto/stubs/uri_pattern.rb +23 -0
- data/lib/pacto/stubs/webmock_adapter.rb +69 -0
- data/lib/pacto/stubs/webmock_helper.rb +71 -0
- data/lib/pacto/ui.rb +7 -0
- data/lib/pacto/uri.rb +9 -0
- data/lib/pacto/validation.rb +57 -0
- data/lib/pacto/validators/body_validator.rb +41 -0
- data/lib/pacto/validators/request_body_validator.rb +23 -0
- data/lib/pacto/validators/response_body_validator.rb +23 -0
- data/lib/pacto/validators/response_header_validator.rb +49 -0
- data/lib/pacto/validators/response_status_validator.rb +24 -0
- data/lib/pacto/version.rb +1 -1
- data/pacto.gemspec +33 -29
- data/resources/contract_schema.json +8 -176
- data/resources/draft-03.json +174 -0
- data/spec/integration/data/strict_contract.json +2 -2
- data/spec/integration/e2e_spec.rb +22 -31
- data/spec/integration/rspec_spec.rb +94 -0
- data/spec/integration/templating_spec.rb +9 -12
- data/{lib → spec}/pacto/server.rb +0 -0
- data/{lib → spec}/pacto/server/dummy.rb +11 -8
- data/{lib → spec}/pacto/server/playback_servlet.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/hooks/erb_hook_spec.rb +15 -15
- data/spec/unit/pacto/configuration_spec.rb +2 -10
- data/spec/unit/pacto/contract_factory_spec.rb +16 -13
- data/spec/unit/pacto/contract_files_spec.rb +42 -0
- data/spec/unit/pacto/contract_list_spec.rb +35 -0
- data/spec/unit/pacto/contract_spec.rb +43 -44
- data/spec/unit/pacto/contract_validator_spec.rb +85 -0
- data/spec/unit/pacto/core/configuration_spec.rb +4 -11
- data/spec/unit/pacto/core/contract_registry_spec.rb +119 -0
- data/spec/unit/pacto/core/modes_spec.rb +18 -0
- data/spec/unit/pacto/core/validation_registry_spec.rb +76 -0
- data/spec/unit/pacto/core/validation_spec.rb +60 -0
- data/spec/unit/pacto/extensions_spec.rb +14 -23
- data/spec/unit/pacto/generator/filters_spec.rb +99 -0
- data/spec/unit/pacto/generator_spec.rb +34 -73
- data/spec/unit/pacto/meta_schema_spec.rb +46 -6
- data/spec/unit/pacto/pacto_spec.rb +17 -15
- data/spec/unit/pacto/{request_spec.rb → request_clause_spec.rb} +32 -44
- data/spec/unit/pacto/request_pattern_spec.rb +22 -0
- data/spec/unit/pacto/response_clause_spec.rb +54 -0
- data/spec/unit/pacto/stubs/uri_pattern_spec.rb +28 -0
- data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +205 -0
- data/spec/unit/pacto/stubs/webmock_helper_spec.rb +20 -0
- data/spec/unit/pacto/uri_spec.rb +20 -0
- data/spec/unit/pacto/validators/body_validator_spec.rb +105 -0
- data/spec/unit/pacto/validators/response_header_validator_spec.rb +94 -0
- data/spec/unit/pacto/validators/response_status_validator_spec.rb +20 -0
- metadata +230 -146
- data/features/generation/generation.feature +0 -25
- data/lib/pacto/core/contract_repository.rb +0 -44
- data/lib/pacto/hash_merge_processor.rb +0 -14
- data/lib/pacto/request.rb +0 -57
- data/lib/pacto/response.rb +0 -63
- data/lib/pacto/response_adapter.rb +0 -24
- data/lib/pacto/stubs/built_in.rb +0 -57
- data/spec/unit/pacto/core/contract_repository_spec.rb +0 -133
- data/spec/unit/pacto/hash_merge_processor_spec.rb +0 -20
- data/spec/unit/pacto/response_adapter_spec.rb +0 -25
- data/spec/unit/pacto/response_spec.rb +0 -201
- data/spec/unit/pacto/stubs/built_in_spec.rb +0 -168
@@ -1,37 +1,54 @@
|
|
1
|
-
Feature:
|
2
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
+
"""
|
data/lib/pacto.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'pacto/version'
|
2
2
|
|
3
|
-
require '
|
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/
|
20
|
-
require 'pacto/
|
21
|
-
require 'pacto/
|
22
|
-
require 'pacto/stubs/
|
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/
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
90
|
+
def load_contract(contract_path, host)
|
91
|
+
load_contracts(contract_path, host).contracts.first
|
92
|
+
end
|
71
93
|
|
72
|
-
|
73
|
-
|
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
|
data/lib/pacto/contract.rb
CHANGED
@@ -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!
|
14
|
+
def stub_contract!(values = {})
|
13
15
|
@values = values
|
14
|
-
|
16
|
+
Pacto.configuration.provider.stub_request!(request, response)
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
@
|
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
|
22
|
-
|
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
|
-
|
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 =
|
11
|
-
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 :
|
4
|
-
|
3
|
+
attr_accessor :provider, :strict_matchers,
|
4
|
+
:contracts_path, :logger, :generator_options
|
5
|
+
attr_reader :hook
|
5
6
|
|
6
7
|
def initialize
|
7
|
-
@
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
22
|
-
Pacto.register_contract(contract, *tags)
|
23
|
-
end
|
26
|
+
private
|
24
27
|
|
25
|
-
def
|
26
|
-
if
|
27
|
-
@
|
28
|
+
def define_logger_level
|
29
|
+
if ENV['PACTO_DEBUG']
|
30
|
+
@logger.level = :debug
|
28
31
|
else
|
29
|
-
|
30
|
-
@callback = callback
|
32
|
+
@logger.level = :default
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|