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
data/lib/pacto/generator.rb
CHANGED
@@ -1,66 +1,44 @@
|
|
1
|
-
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'pacto/generator/native_contract_generator'
|
3
|
+
require 'pacto/generator/hint'
|
2
4
|
|
3
5
|
module Pacto
|
4
|
-
|
5
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
24
|
+
def configure
|
25
|
+
yield(configuration)
|
26
|
+
end
|
34
27
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
61
|
-
|
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
|
-
|
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(
|
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
|
data/lib/pacto/hooks/erb_hook.rb
CHANGED
@@ -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!(:
|
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
|
data/lib/pacto/logger.rb
CHANGED
data/lib/pacto/meta_schema.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
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
|
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
|