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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +29 -7
- data/.travis.yml +8 -1
- data/CONTRIBUTING.md +3 -6
- data/Gemfile +13 -2
- data/Guardfile +4 -4
- data/Procfile +1 -0
- data/README.md +47 -13
- data/Rakefile +66 -19
- data/TODO.md +33 -10
- data/bin/pacto +4 -0
- data/changelog.md +30 -0
- data/docs/configuration.md +69 -0
- data/docs/consumer.md +18 -0
- data/docs/cops.md +39 -0
- data/docs/forensics.md +66 -0
- data/docs/generation.md +65 -0
- data/docs/rake_tasks.md +10 -0
- data/docs/rspec.md +0 -0
- data/docs/samples.md +133 -0
- data/docs/server.md +34 -0
- data/docs/server_cli.md +18 -0
- data/docs/stenographer.md +20 -0
- data/features/configuration/strict_matchers.feature +10 -10
- data/features/evolve/existing_services.feature +12 -10
- data/features/generate/generation.feature +11 -11
- data/features/steps/pacto_steps.rb +17 -12
- data/features/stub/templates.feature +4 -4
- data/features/support/env.rb +21 -9
- data/features/validate/meta_validation.feature +9 -17
- data/features/validate/validation.feature +5 -6
- data/lib/pacto.rb +41 -33
- data/lib/pacto/actor.rb +5 -0
- data/lib/pacto/actors/from_examples.rb +67 -0
- data/lib/pacto/actors/json_generator.rb +20 -0
- data/lib/pacto/cli.rb +75 -0
- data/lib/pacto/cli/helpers.rb +20 -0
- data/lib/pacto/consumer.rb +80 -0
- data/lib/pacto/consumer/faraday_driver.rb +34 -0
- data/lib/pacto/contract.rb +48 -20
- data/lib/pacto/contract_builder.rb +125 -0
- data/lib/pacto/contract_factory.rb +31 -12
- data/lib/pacto/contract_files.rb +1 -0
- data/lib/pacto/contract_set.rb +12 -0
- data/lib/pacto/cops.rb +46 -0
- data/lib/pacto/cops/body_cop.rb +23 -0
- data/lib/pacto/cops/request_body_cop.rb +10 -0
- data/lib/pacto/cops/response_body_cop.rb +10 -0
- data/lib/pacto/{validators/response_header_validator.rb → cops/response_header_cop.rb} +9 -15
- data/lib/pacto/cops/response_status_cop.rb +18 -0
- data/lib/pacto/core/configuration.rb +16 -5
- data/lib/pacto/core/contract_registry.rb +13 -32
- data/lib/pacto/core/hook.rb +1 -0
- data/lib/pacto/core/http_middleware.rb +23 -0
- data/lib/pacto/core/investigation_registry.rb +60 -0
- data/lib/pacto/core/modes.rb +1 -0
- data/lib/pacto/core/pacto_request.rb +59 -0
- data/lib/pacto/core/pacto_response.rb +41 -0
- data/lib/pacto/dash.rb +9 -0
- data/lib/pacto/erb_processor.rb +1 -0
- data/lib/pacto/exceptions/invalid_contract.rb +1 -0
- data/lib/pacto/extensions.rb +3 -16
- data/lib/pacto/forensics/investigation_filter.rb +90 -0
- data/lib/pacto/forensics/investigation_matcher.rb +80 -0
- data/lib/pacto/generator.rb +31 -53
- data/lib/pacto/generator/filters.rb +8 -7
- data/lib/pacto/generator/hint.rb +26 -0
- data/lib/pacto/generator/native_contract_generator.rb +74 -0
- data/lib/pacto/hooks/erb_hook.rb +2 -1
- data/lib/pacto/investigation.rb +49 -0
- data/lib/pacto/logger.rb +1 -0
- data/lib/pacto/meta_schema.rb +12 -6
- data/lib/pacto/native_contract_factory.rb +60 -0
- data/lib/pacto/observers/stenographer.rb +42 -0
- data/lib/pacto/provider.rb +27 -0
- data/lib/pacto/rake_task.rb +25 -70
- data/lib/pacto/request_clause.rb +31 -29
- data/lib/pacto/request_pattern.rb +20 -3
- data/lib/pacto/resettable.rb +22 -0
- data/lib/pacto/response_clause.rb +5 -12
- data/lib/pacto/rspec.rb +38 -31
- data/lib/pacto/server.rb +4 -0
- data/lib/pacto/stubs/uri_pattern.rb +21 -11
- data/lib/pacto/stubs/webmock_adapter.rb +69 -34
- data/lib/pacto/swagger_contract_factory.rb +90 -0
- data/lib/pacto/test_helper.rb +37 -0
- data/lib/pacto/ui.rb +32 -2
- data/lib/pacto/uri.rb +2 -1
- data/lib/pacto/version.rb +2 -1
- data/pacto-server.gemspec +24 -0
- data/pacto.gemspec +13 -9
- data/resources/contract_schema.json +46 -18
- data/resources/draft-04.json +150 -0
- data/sample_apis/album/cover_api.rb +12 -0
- data/sample_apis/config.ru +25 -0
- data/sample_apis/echo_api.rb +26 -0
- data/sample_apis/files_api.rb +50 -0
- data/sample_apis/hello_api.rb +14 -0
- data/sample_apis/ping_api.rb +11 -0
- data/sample_apis/reverse_api.rb +20 -0
- data/samples/README.md +11 -0
- data/samples/Rakefile +2 -0
- data/samples/configuration.rb +33 -0
- data/samples/consumer.rb +15 -0
- data/samples/contracts/README.md +1 -0
- data/samples/contracts/contract.js +93 -0
- data/samples/contracts/get_album_cover.json +48 -0
- data/samples/contracts/localhost/api/echo.json +37 -0
- data/samples/contracts/localhost/api/ping.json +38 -0
- data/samples/cops.rb +30 -0
- data/samples/forensics.rb +54 -0
- data/samples/generation.rb +48 -0
- data/samples/rake_tasks.sh +7 -0
- data/samples/rspec.rb +1 -0
- data/samples/samples.rb +92 -0
- data/samples/scripts/bootstrap +2 -0
- data/samples/scripts/wrapper +11 -0
- data/samples/server.rb +24 -0
- data/samples/server_cli.sh +12 -0
- data/samples/stenographer.rb +17 -0
- data/spec/coveralls_helper.rb +1 -0
- data/spec/fabricators/contract_fabricator.rb +94 -0
- data/spec/fabricators/http_fabricator.rb +48 -0
- data/spec/fabricators/webmock_fabricator.rb +24 -0
- data/spec/{unit/data → fixtures/contracts}/contract.json +2 -2
- data/spec/fixtures/contracts/contract_with_examples.json +58 -0
- data/spec/{unit/data → fixtures/contracts}/simple_contract.json +5 -3
- data/spec/{integration/data → fixtures/contracts}/strict_contract.json +5 -3
- data/spec/{integration/data → fixtures/contracts}/templating_contract.json +3 -2
- data/spec/{integration/data/simple_contract.json → fixtures/deprecated_contracts/deprecated_contract.json} +2 -1
- data/spec/fixtures/swagger/petstore.yaml +101 -0
- data/spec/integration/e2e_spec.rb +19 -20
- data/spec/integration/forensics/integration_matcher_spec.rb +90 -0
- data/spec/integration/rspec_spec.rb +22 -25
- data/spec/integration/templating_spec.rb +7 -6
- data/spec/pacto/dummy_server.rb +4 -0
- data/spec/pacto/{server → dummy_server}/dummy.rb +7 -6
- data/spec/pacto/dummy_server/jruby_workaround_helper.rb +23 -0
- data/spec/pacto/{server → dummy_server}/playback_servlet.rb +3 -2
- data/spec/spec_helper.rb +16 -7
- data/spec/unit/actors/from_examples_spec.rb +70 -0
- data/spec/unit/actors/json_generator_spec.rb +105 -0
- data/spec/unit/pacto/actor_spec.rb +23 -0
- data/spec/unit/pacto/configuration_spec.rb +7 -6
- data/spec/unit/pacto/consumer/faraday_driver_spec.rb +40 -0
- data/spec/unit/pacto/contract_builder_spec.rb +89 -0
- data/spec/unit/pacto/contract_factory_spec.rb +62 -11
- data/spec/unit/pacto/contract_files_spec.rb +1 -0
- data/spec/unit/pacto/contract_set_spec.rb +36 -0
- data/spec/unit/pacto/contract_spec.rb +51 -39
- data/spec/unit/pacto/cops/body_cop_spec.rb +107 -0
- data/spec/unit/pacto/{validators/response_header_validator_spec.rb → cops/response_header_cop_spec.rb} +30 -19
- data/spec/unit/pacto/cops/response_status_cop_spec.rb +26 -0
- data/spec/unit/pacto/cops_spec.rb +75 -0
- data/spec/unit/pacto/core/configuration_spec.rb +6 -5
- data/spec/unit/pacto/core/contract_registry_spec.rb +16 -83
- data/spec/unit/pacto/core/http_middleware_spec.rb +36 -0
- data/spec/unit/pacto/core/investigation_spec.rb +62 -0
- data/spec/unit/pacto/core/modes_spec.rb +5 -4
- data/spec/unit/pacto/erb_processor_spec.rb +3 -2
- data/spec/unit/pacto/extensions_spec.rb +10 -20
- data/spec/unit/pacto/generator/filters_spec.rb +11 -10
- data/spec/unit/pacto/generator/native_contract_generator_spec.rb +171 -0
- data/spec/unit/{hooks → pacto/hooks}/erb_hook_spec.rb +18 -11
- data/spec/unit/pacto/investigation_registry_spec.rb +77 -0
- data/spec/unit/pacto/logger_spec.rb +6 -5
- data/spec/unit/pacto/meta_schema_spec.rb +5 -4
- data/spec/unit/pacto/native_contract_factory_spec.rb +26 -0
- data/spec/unit/pacto/pacto_spec.rb +13 -28
- data/spec/unit/pacto/request_clause_spec.rb +16 -51
- data/spec/unit/pacto/request_pattern_spec.rb +6 -5
- data/spec/unit/pacto/response_clause_spec.rb +6 -19
- data/spec/unit/pacto/server/playback_servlet_spec.rb +21 -18
- data/spec/unit/pacto/stubs/observers/stenographer_spec.rb +33 -0
- data/spec/unit/pacto/stubs/uri_pattern_spec.rb +39 -11
- data/spec/unit/pacto/stubs/webmock_adapter_spec.rb +67 -117
- data/spec/unit/pacto/swagger_contract_factory_spec.rb +56 -0
- data/spec/unit/pacto/uri_spec.rb +1 -0
- data/tasks/release.rake +57 -0
- metadata +247 -76
- data/.rubocop-todo.yml +0 -24
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/CHANGELOG +0 -12
- data/features/validate/body_only.feature +0 -85
- data/lib/pacto/contract_list.rb +0 -17
- data/lib/pacto/contract_validator.rb +0 -29
- data/lib/pacto/core/validation_registry.rb +0 -40
- data/lib/pacto/stubs/webmock_helper.rb +0 -69
- data/lib/pacto/validation.rb +0 -54
- data/lib/pacto/validators/body_validator.rb +0 -49
- data/lib/pacto/validators/request_body_validator.rb +0 -26
- data/lib/pacto/validators/response_body_validator.rb +0 -26
- data/lib/pacto/validators/response_status_validator.rb +0 -24
- data/spec/pacto/server.rb +0 -2
- data/spec/unit/pacto/contract_list_spec.rb +0 -35
- data/spec/unit/pacto/contract_validator_spec.rb +0 -85
- data/spec/unit/pacto/core/validation_registry_spec.rb +0 -76
- data/spec/unit/pacto/core/validation_spec.rb +0 -60
- data/spec/unit/pacto/generator_spec.rb +0 -132
- data/spec/unit/pacto/stubs/webmock_helper_spec.rb +0 -20
- data/spec/unit/pacto/validators/body_validator_spec.rb +0 -118
- 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
|
-
"
|
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
|
-
"
|
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
|
30
|
+
Then the stdout should contain:
|
31
31
|
""""
|
32
|
-
Validating contracts
|
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
|
"""
|
data/lib/pacto.rb
CHANGED
@@ -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/
|
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/
|
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/
|
38
|
-
require 'pacto/request_pattern'
|
51
|
+
require 'pacto/contract_set'
|
39
52
|
require 'pacto/uri'
|
40
53
|
|
41
|
-
#
|
42
|
-
require 'pacto/
|
43
|
-
require 'pacto/
|
44
|
-
require 'pacto/
|
45
|
-
require 'pacto/
|
46
|
-
require 'pacto/
|
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.
|
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).
|
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
|
-
|
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
|
-
|
108
|
+
ContractSet.new(contracts)
|
101
109
|
end
|
102
110
|
end
|
103
111
|
end
|
data/lib/pacto/actor.rb
ADDED
@@ -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
|
data/lib/pacto/cli.rb
ADDED
@@ -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
|