pacto-server 0.4.0.rc1
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.
- checksums.yaml +7 -0
- data/bin/pacto-server +9 -0
- data/lib/pacto/server/api.rb +102 -0
- data/lib/pacto/server/config.rb +51 -0
- data/lib/pacto/server/settings.rb +29 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 118b6d643b8f3a10e313cf65837f2f58d03f0d44
|
4
|
+
data.tar.gz: 063e07bd7f75b897ffe798c9591f43d67ac624bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cef2e211096315d6b92487bddc64bb3401f2ece8fe4cc777314ab798125117073fe0084189f4b0c09be572f77033bc83a6a1630872a8d6f934f5ed820950eb68
|
7
|
+
data.tar.gz: 54a2f790dbe2489fc0470b285f65cf6e2de8ca584e7f0a1067be6bf77e03af208530cf28dda580e19d953cdbd578f9fc24510378b8ed06907d45111ae05f3e81
|
data/bin/pacto-server
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'goliath/api'
|
3
|
+
require 'goliath/runner'
|
4
|
+
require 'pacto/server'
|
5
|
+
|
6
|
+
runner = Goliath::Runner.new(ARGV, Pacto::Server::API.new(:pwd => Dir.pwd))
|
7
|
+
runner.log_file = File.expand_path(runner.log_file, Dir.pwd) if runner.log_file
|
8
|
+
runner.app = Goliath::Rack::Builder.build(Pacto::Server::API, runner.api)
|
9
|
+
runner.run
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'pacto/server/settings'
|
3
|
+
|
4
|
+
module Pacto
|
5
|
+
module Server
|
6
|
+
class API < Goliath::API
|
7
|
+
include Pacto::Server::Settings
|
8
|
+
use ::Rack::ContentLength
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
@original_pwd = Dir.pwd
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_headers(env, headers)
|
16
|
+
env.logger.debug 'receiving headers: ' + headers.inspect
|
17
|
+
env['client-headers'] = headers
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_body(env, data)
|
21
|
+
env.logger.debug 'received data: ' + data
|
22
|
+
(env['async-body'] ||= '') << data
|
23
|
+
end
|
24
|
+
|
25
|
+
def response(env)
|
26
|
+
log_request(env)
|
27
|
+
req = prepare_pacto_request(env)
|
28
|
+
env.logger.info "sending: #{req}"
|
29
|
+
resp = Pacto::Consumer::FaradayDriver.new.execute(req)
|
30
|
+
process_pacto_response resp, env
|
31
|
+
rescue => e
|
32
|
+
backtrace = e.backtrace.join("\n")
|
33
|
+
env.logger.warn "responding with error: #{e.message}, backtrace: #{backtrace}"
|
34
|
+
[500, {}, e.message]
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def log_request(env)
|
40
|
+
method = env['REQUEST_METHOD'].upcase
|
41
|
+
env.logger.info "received: #{method} #{env['REQUEST_URI']} with headers #{env['client-headers']}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def prepare_pacto_request(env)
|
45
|
+
PactoRequest.new(
|
46
|
+
body: env['async-body'],
|
47
|
+
headers: filter_request_headers(env),
|
48
|
+
method: env['REQUEST_METHOD'].downcase.to_sym,
|
49
|
+
uri: determine_proxy_uri(env)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_pacto_response(resp, _env)
|
54
|
+
code = resp.status
|
55
|
+
safe_response_headers = normalize_headers(resp.headers).reject { |k, _v| %w(connection content-encoding content-length transfer-encoding).include? k.downcase }
|
56
|
+
body = proxy_rewrite(resp.body)
|
57
|
+
env.logger.info "received response: #{resp.inspect}"
|
58
|
+
env.logger.debug "response body: #{body}"
|
59
|
+
[code, safe_response_headers, body]
|
60
|
+
end
|
61
|
+
|
62
|
+
def determine_proxy_uri(env)
|
63
|
+
path = env[Goliath::Request::REQUEST_PATH]
|
64
|
+
if env.config[:backend_host]
|
65
|
+
uri = Addressable::URI.heuristic_parse("#{env.config[:backend_host]}#{path}")
|
66
|
+
else
|
67
|
+
host = env['HTTP_HOST']
|
68
|
+
host.gsub!('.dev', '.com') if env.config[:strip_dev]
|
69
|
+
uri = Addressable::URI.heuristic_parse("https://#{host}#{path}")
|
70
|
+
uri.port = nil if env.config[:strip_port]
|
71
|
+
end
|
72
|
+
uri
|
73
|
+
end
|
74
|
+
|
75
|
+
def filter_request_headers(env)
|
76
|
+
headers = env['client-headers']
|
77
|
+
safe_headers = headers.reject { |k, _v| %w(host content-length transfer-encoding).include? k.downcase }
|
78
|
+
env.logger.debug "filtered headers: #{safe_headers}"
|
79
|
+
safe_headers
|
80
|
+
end
|
81
|
+
|
82
|
+
def port
|
83
|
+
env.config[:port]
|
84
|
+
end
|
85
|
+
|
86
|
+
def normalize_headers(headers)
|
87
|
+
headers.each_with_object({}) do |elem, res|
|
88
|
+
key = elem.first.dup
|
89
|
+
value = elem.last
|
90
|
+
key.gsub!('_', '-')
|
91
|
+
key = key.split('-').map(&:capitalize).join '-'
|
92
|
+
res[key] = value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def proxy_rewrite(body)
|
97
|
+
# FIXME: How I usually deal with rels, but others may not want this behavior.
|
98
|
+
body.gsub('.com', ".dev:#{port}").gsub(/https\:([\w\-\.\\\/]+).dev/, 'http:\1.dev')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
def token_map
|
3
|
+
if File.readable? '.tokens.json'
|
4
|
+
MultiJson.load(File.read '.tokens.json')
|
5
|
+
else
|
6
|
+
{}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare_contracts(contracts)
|
11
|
+
contracts.stub_providers if options[:stub]
|
12
|
+
end
|
13
|
+
|
14
|
+
config[:backend_host] = options[:backend_host]
|
15
|
+
config[:strip_port] = options[:strip_port]
|
16
|
+
config[:strip_dev] = options[:strip_dev]
|
17
|
+
config[:port] = port
|
18
|
+
contracts_path = options[:directory] || File.expand_path('contracts', Dir.pwd)
|
19
|
+
Pacto.configure do |pacto_config|
|
20
|
+
pacto_config.logger = options[:pacto_logger] || logger
|
21
|
+
pacto_config.loggerl.log_level = config[:pacto_log_level] if config[:pacto_log_level]
|
22
|
+
pacto_config.contracts_path = contracts_path
|
23
|
+
pacto_config.strict_matchers = options[:strict]
|
24
|
+
pacto_config.generator_options = {
|
25
|
+
schema_version: :draft3,
|
26
|
+
token_map: token_map
|
27
|
+
}
|
28
|
+
pacto_config.stenographer_log_file = options[:stenographer_log_file]
|
29
|
+
end
|
30
|
+
|
31
|
+
if options[:generate]
|
32
|
+
Pacto.generate!
|
33
|
+
logger.info 'Pacto generation mode enabled'
|
34
|
+
end
|
35
|
+
|
36
|
+
if options[:recursive_loading]
|
37
|
+
Dir["#{contracts_path}/*"].each do |host_dir|
|
38
|
+
host = File.basename host_dir
|
39
|
+
prepare_contracts Pacto.load_contracts(host_dir, "https://#{host}", options[:format])
|
40
|
+
end
|
41
|
+
else
|
42
|
+
host_pattern = options[:backend_host] || 'https://{server}'
|
43
|
+
prepare_contracts Pacto.load_contracts(contracts_path, host_pattern, options[:format])
|
44
|
+
end
|
45
|
+
|
46
|
+
Pacto.validate! if options[:validate]
|
47
|
+
|
48
|
+
if options[:live]
|
49
|
+
# WebMock.reset!
|
50
|
+
WebMock.allow_net_connect!
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Pacto
|
3
|
+
module Server
|
4
|
+
module Settings
|
5
|
+
def options_parser(opts, options) # rubocop:disable MethodLength
|
6
|
+
options[:format] ||= :default
|
7
|
+
options[:strict] ||= false
|
8
|
+
options[:directory] ||= File.expand_path('contracts', @original_pwd)
|
9
|
+
options[:config] ||= File.expand_path('../config.rb', __FILE__)
|
10
|
+
options[:stenographer_log_file] ||= File.expand_path('pacto_stenographer.log', @original_pwd)
|
11
|
+
options[:strip_port] ||= true
|
12
|
+
|
13
|
+
opts.on('-l', '--live', 'Send requests to live services (instead of stubs)') { |_val| options[:live] = true }
|
14
|
+
opts.on('-f', '--format FORMAT', 'Contract format') { |val| options[:format] = val }
|
15
|
+
opts.on('--stub', 'Stub responses based on contracts') { |_val| options[:stub] = true }
|
16
|
+
opts.on('-g', '--generate', 'Generate Contracts from requests') { |_val| options[:generate] = true }
|
17
|
+
opts.on('-V', '--validate', 'Validate requests/responses against Contracts') { |_val| options[:validate] = true }
|
18
|
+
opts.on('-m', '--match-strict', 'Enforce strict request matching rules') { |_val| options[:strict] = true }
|
19
|
+
opts.on('-x', '--contracts_dir DIR', 'Directory that contains the contracts to be registered') { |val| options[:directory] = File.expand_path(val, @original_pwd) }
|
20
|
+
opts.on('-H', '--host HOST', 'Host of the real service, for generating or validating live requests') { |val| options[:backend_host] = val }
|
21
|
+
opts.on('-r', '--recursive-loading', 'Load contracts from folders named after the host to be stubbed') { |_val| options[:recursive_loading] = true }
|
22
|
+
opts.on('--strip-port', 'Strip the port from the request URI to build the proxied URI') { |_val| options[:strip_port] = true }
|
23
|
+
opts.on('--strip-dev', 'Strip .dev from the request domain to build the proxied URI') { |_val| options[:strip_dev] = true }
|
24
|
+
opts.on('--stenographer-log-file', 'Location for the stenographer log file') { |val| options[:stenographer_log_file] = val }
|
25
|
+
opts.on('--log-level [LEVEL]', [:debug, :info, :warn, :error, :fatal], 'Pacto log level ( debug, info, warn, error or fatal)') { |val| options[:pacto_log_level] = val }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pacto-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ThoughtWorks
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pacto
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.4.0.rc1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.4.0.rc1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: goliath
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: em-synchrony
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: em-http-request
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
69
|
+
description: Pacto Server let's you run Pacto as a standalone server to arbitrate
|
70
|
+
contract disputes between a service provider and one or more consumers in any programming
|
71
|
+
language. It's Pacto beyond Ruby
|
72
|
+
email:
|
73
|
+
- pacto-gem@googlegroups.com
|
74
|
+
executables:
|
75
|
+
- pacto-server
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- bin/pacto-server
|
80
|
+
- lib/pacto/server/api.rb
|
81
|
+
- lib/pacto/server/config.rb
|
82
|
+
- lib/pacto/server/settings.rb
|
83
|
+
homepage: http://thoughtworks.github.io/pacto/
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 1.3.1
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Polyglot Integration Contract Testing server
|
107
|
+
test_files: []
|