Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Pacto Server: replace Goliath with Reel #170

Merged
merged 10 commits into from
Feb 17, 2015
Merged
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
version: 0.0.{build}
init:
- choco install openssl.light
- gem install bundler --quiet --no-ri --no-rdoc
- gem install foreman --quiet --no-ri --no-rdoc
install:
Expand Down
11 changes: 2 additions & 9 deletions bin/pacto-server
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
#!/usr/bin/env ruby
require 'goliath/api'
require 'goliath/runner'
require 'pacto/server'
require 'pacto/server/cli'

Pacto::Server::API.original_pwd = Dir.pwd
runner = Goliath::Runner.new(ARGV, Pacto::Server::API.new(:pwd => Dir.pwd))
runner.log_file = File.expand_path(runner.log_file, Dir.pwd) if runner.log_file
runner.app = Goliath::Rack::Builder.build(Pacto::Server::API, runner.api)
Goliath.run_app_on_exit = false
runner.run
Pacto::Server::CLI.start
18 changes: 8 additions & 10 deletions features/generate/generation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ Feature: Contract Generation
Background:
Given a file named "requests/my_contract.json" with:
"""
{
{
"request": {
"http_method": "GET",
"path": "/hello",
"path": "/api/ping",
"headers": {
"Accept": "application/json"
}
Expand All @@ -29,7 +29,7 @@ Feature: Contract Generation

Scenario: Generating a contract using the rake task
Given a directory named "contracts"
When I successfully run `bundle exec rake pacto:generate['tmp/aruba/requests','tmp/aruba/contracts','http://localhost:8000']`
When I successfully run `bundle exec rake pacto:generate['tmp/aruba/requests','tmp/aruba/contracts','http://localhost:5000']`
Then the stdout should contain "Successfully generated all contracts"

Scenario: Generating a contract programmatically
Expand All @@ -40,25 +40,23 @@ Feature: Contract Generation

WebMock.allow_net_connect!
generator = Pacto::Generator.contract_generator
contract = generator.generate_from_partial_contract('requests/my_contract.json', 'http://localhost:8000')
contract = generator.generate_from_partial_contract('requests/my_contract.json', 'http://localhost:5000')
puts contract
"""
When I successfully run `bundle exec ruby generate.rb`
Then the stdout should match this contract:
"""json
{
"name": "/hello",
"name": "/api/ping",
"request": {
"headers": {
"Accept": "application/json"
},
"http_method": "get",
"path": "/hello"
"path": "/api/ping"
},
"response": {
"headers": {
"Content-Type": "application/json",
"Vary": "Accept"
"Content-Type": "application/json"
},
"status": 200,
"schema": {
Expand All @@ -67,7 +65,7 @@ Feature: Contract Generation
"type": "object",
"required": true,
"properties": {
"message": {
"ping": {
"type": "string",
"required": true
}
Expand Down
10 changes: 3 additions & 7 deletions features/support/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
require 'json_spec/cucumber'
require 'aruba/jruby' if RUBY_PLATFORM == 'java'
require 'pacto/test_helper'
require_relative '../../spec/pacto/dummy_server'

Pacto.configuration.hide_deprecations = true

Expand All @@ -16,19 +15,16 @@

class PactoWorld
include Pacto::TestHelper
include Pacto::DummyServer::JRubyWorkaroundHelper
end

World do
PactoWorld.new
end

Around do | _scenario, block |
# This is a cucumber bug (see cucumber #640)
WebMock.allow_net_connect!
world = self || PactoWorld.new
world.run_pacto do
Bundler.with_clean_env do
block.call
end
world.with_pacto(port: 8000, live: true, backend_host: 'http://localhost:5000') do
block.call
end
end
6 changes: 3 additions & 3 deletions features/validate/validation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Feature: Validation
{
"request": {
"http_method": "GET",
"path": "/hello",
"path": "/api/hello",
"headers": { "Accept": "application/json" },
"params": {}
},
Expand All @@ -26,10 +26,10 @@ Feature: Validation
}
}
"""
When I successfully run `bundle exec rake pacto:validate['http://localhost:8000','tmp/aruba/contracts/simple_contract.json']`
When I successfully run `bundle exec rake pacto:validate['http://localhost:5000','tmp/aruba/contracts/simple_contract.json']`
Then the stdout should contain:
""""
Validating contracts against host http://localhost:8000
Validating contracts against host http://localhost:5000
OK! simple_contract.json
1 valid contract
"""
23 changes: 15 additions & 8 deletions lib/pacto/core/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ def initialize # rubocop:disable Metrics/MethodLength
@middleware.add_observer Pacto::Cops, :investigate
@generator = Pacto::Generator.contract_generator
@middleware.add_observer @generator, :generate
@stenographer_log_file ||= File.expand_path('pacto_stenographer.log')
@default_consumer = Pacto::Consumer.new
@default_provider = Pacto::Provider.new
@adapter = Stubs::WebMockAdapter.new(@middleware)
@strict_matchers = true
@contracts_path = '.'
@logger = Logger::SimpleLogger.instance
define_logger_level
@hook = Hook.new {}
@generator_options = { schema_version: 'draft3' }
@color = $stdout.tty?
@proxy = ENV['PACTO_PROXY']
end

def logger
@logger ||= new_simple_logger
end

def stenographer_log_file
@stenographer_log_file ||= File.expand_path('pacto_stenographer.log')
end

def register_hook(hook = nil, &block)
if block_given?
@hook = Hook.new(&block)
Expand All @@ -37,11 +42,13 @@ def register_hook(hook = nil, &block)

private

def define_logger_level
if ENV['PACTO_DEBUG']
@logger.level = :debug
else
@logger.level = :default
def new_simple_logger
Logger::SimpleLogger.instance.tap do | logger |
if ENV['PACTO_DEBUG']
logger.level = :debug
else
logger.level = :default
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/pacto/request_clause.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def pattern
def uri(values = {})
values ||= {}
uri_template = pattern.uri_template
missing_keys = uri_template.keys - values.keys
missing_keys = uri_template.keys.map(&:to_sym) - values.keys.map(&:to_sym)
values[:scheme] = 'http' if missing_keys.delete(:scheme)
values[:server] = 'localhost' if missing_keys.delete(:server)
logger.warn "Missing keys for building a complete URL: #{missing_keys.inspect}" unless missing_keys.empty?
Expand Down
43 changes: 41 additions & 2 deletions lib/pacto/server.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
# -*- encoding : utf-8 -*-
require 'goliath'
require 'reel'
require 'pacto'
require 'pacto/server/api'
require_relative 'server/settings'
require_relative 'server/proxy'

module Pacto
module Server
class HTTP < Reel::Server::HTTP
attr_reader :settings, :logger
include Proxy

def initialize(host = '127.0.0.1', port = 3000, options = {})
@settings = Settings::OptionHandler.new(port, @logger).handle(options)
@logger = Pacto.configuration.logger
logger.info "Pacto Server starting on #{host}:#{port}"
super(host, port, spy: options[:spy], &method(:on_connection))
end

def on_connection(connection)
# Support multiple keep-alive requests per connection
connection.each_request do |reel_request|
begin
pacto_request = # exclusive do
Pacto::PactoRequest.new(
headers: reel_request.headers, body: reel_request.read,
method: reel_request.method, uri: Addressable::URI.heuristic_parse(reel_request.uri)
)
# end

pacto_response = proxy_request(pacto_request)
reel_response = ::Reel::Response.new(pacto_response.status, pacto_response.headers, pacto_response.body)
reel_request.respond(reel_response)
rescue WebMock::NetConnectNotAllowedError => e
reel_request.respond(500, e.message)
rescue => e
reel_request.respond(500, e.message)
end
end
end
end
end
end
66 changes: 66 additions & 0 deletions lib/pacto/server/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'thor'
require 'pacto/server'

module Pacto
module Server
class CLI < Thor
class << self
DEFAULTS = {
stdout: true,
log_file: 'pacto.log',
# :config => 'pacto/config/pacto_server.rb',
strict: false,
stub: true,
live: false,
generate: false,
verbose: true,
validate: true,
directory: File.join(Dir.pwd, 'spec', 'fixtures', 'contracts'),
port: 9000,
format: :legacy,
stenographer_log_file: File.expand_path('pacto_stenographer.log', Dir.pwd),
strip_port: true
}

def server_options
method_option :port, default: 4567, desc: 'The port to run the server on'
method_option :directory, default: DEFAULTS[:directory], desc: 'The directory containing contracts'
method_option :strict, default: DEFAULTS[:strict], desc: 'Whether Pacto should use strict matching or not'
method_option :format, default: DEFAULTS[:format], desc: 'The contract format to use'
method_option :strip_port, default: DEFAULTS[:strip_port], desc: 'If pacto should remove the port from URLs before forwarding'
end
end

desc 'stub [CONTRACTS...]', 'Launches a stub server for a set of contracts'
method_option :port, type: :numeric, desc: 'The port to listen on', default: 3000
method_option :spy, type: :boolean, desc: 'Display traffic received by Pacto'
server_options
def stub(*_contracts)
setup_interrupt
server_options = @options.dup
server_options[:stub] = true
Pacto::Server::HTTP.run('0.0.0.0', options.port, server_options)
end

desc 'proxy [CONTRACTS...]', 'Launches an intercepting proxy server for a set of contracts'
method_option :to, type: :string, desc: 'The target host for forwarded requests'
method_option :port, type: :numeric, desc: 'The port to listen on', default: 3000
method_option :spy, type: :boolean, desc: 'Display traffic received by Pacto'
def proxy(*_contracts)
setup_interrupt
server_options = @options.dup
server_options[:live] = true
Pacto::Server::HTTP.run('0.0.0.0', options.port, server_options)
end

private

def setup_interrupt
trap('INT') do
say 'Exiting...'
exit
end
end
end
end
end
51 changes: 1 addition & 50 deletions lib/pacto/server/config.rb
Original file line number Diff line number Diff line change
@@ -1,51 +1,2 @@
# -*- encoding : utf-8 -*-
def token_map
if File.readable? '.tokens.json'
MultiJson.load(File.read '.tokens.json')
else
{}
end
end

def prepare_contracts(contracts)
contracts.stub_providers if options[:stub]
end

config[:backend_host] = options[:backend_host]
config[:strip_port] = options[:strip_port]
config[:strip_dev] = options[:strip_dev]
config[:port] = port
contracts_path = options[:directory] || File.expand_path('contracts', Dir.pwd)
Pacto.configure do |pacto_config|
pacto_config.logger = options[:pacto_logger] || logger
pacto_config.loggerl.log_level = config[:pacto_log_level] if config[:pacto_log_level]
pacto_config.contracts_path = contracts_path
pacto_config.strict_matchers = options[:strict]
pacto_config.generator_options = {
schema_version: :draft3,
token_map: token_map
}
pacto_config.stenographer_log_file = options[:stenographer_log_file]
end

if options[:generate]
Pacto.generate!
logger.info 'Pacto generation mode enabled'
end

if options[:recursive_loading]
Dir["#{contracts_path}/*"].each do |host_dir|
host = File.basename host_dir
prepare_contracts Pacto.load_contracts(host_dir, "https://#{host}", options[:format])
end
else
host_pattern = options[:backend_host] || 'https://{server}'
prepare_contracts Pacto.load_contracts(contracts_path, host_pattern, options[:format])
end

Pacto.validate! if options[:validate]

if options[:live]
# WebMock.reset!
WebMock.allow_net_connect!
end
Pacto::Server::Settings::OptionHandler.new(port, logger, config).handle(options)
Loading