Skip to content

Commit

Permalink
Conflicts:
Browse files Browse the repository at this point in the history
	lib/pact/consumer/mock_service/app.rb
	lib/pact/mock_service/cli.rb
	pact-mock-service.gemspec
	spec/support/integration_spec_support.rb
  • Loading branch information
bethesque committed Jan 24, 2015
1 parent 6fa31d9 commit 09425ac
Show file tree
Hide file tree
Showing 22 changed files with 456 additions and 80 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ Do this to generate your change history

git log --pretty=format:' * %h - %s (%an, %ad)'

### 0.2.3 (21 Jan 2015)

* 560671e - Add support for using Pact::Terms in the path (Beth, Wed Jan 21 07:42:10 2015 +1100)
* 4324a97 - Renamed --cors-enabled to --cors (Beth, Wed Jan 21 07:28:34 2015 +1100)
* 5f5ee7e - Set Access-Control-Allow-Origin header to the the request Origin when populated. (Beth, Tue Jan 13 16:03:37 2015 +1100)

### 0.2.3.rc2 (13 Jan 2015)

* daf0696 - Added --consumer and --provider options to CLI. Automatically write pact if both options are given at startup. (Beth, Mon Jan 5 20:48:47 2015 +1100)
* 351c44e - Write pact on shutdown (Beth, Mon Jan 5 17:17:24 2015 +1100)
* e206c9f - Adding cross domain headers (André Allavena, Tue Dec 23 18:01:46 2014 +1000)

### 0.2.3.rc1 (3 Jan 2015)

* afd9cf3 - Removed awesome print gem dependency. (Beth, Sat Jan 3 16:49:40 2015 +1100)
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ As the Pact mock service can be used as a standalone executable and administered

## Usage

### With Ruby
### With Ruby on Mac OSX and Linux

$ gem install pact-mock_service
$ pact-mock-service --port 1234

Run `pact-mock-service help` for command line options.

### With Ruby on Windows

Check out the wiki page [here][install-windows].

#### With SSL

If you need to use the mock service with HTTPS, you can use the built-in SSL mode which relies on a self-signed certificate.
Expand All @@ -43,3 +47,4 @@ See [CONTRIBUTING.md](/CONTRIBUTING.md)
[javascript]: https://github.com/DiUS/pact-consumer-js-dsl
[pact-dev]: https://groups.google.com/forum/#!forum/pact-dev
[windows]: https://github.com/bethesque/pact-mock_service/wiki/Building-a-Windows-standalone-executable
[install-windows]: https://github.com/bethesque/pact-mock_service/wiki/Installing-the-pact-mock_service-gem-on-Windows
134 changes: 77 additions & 57 deletions lib/pact/consumer/mock_service/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
require 'pact/consumer/mock_service/verification_get'
require 'pact/consumer/mock_service/log_get'
require 'pact/consumer/mock_service/pact_post'
require 'pact/consumer/mock_service/options'
require 'pact/consumer/mock_service/cors_origin_header_middleware'
require 'pact/support'

module Pact
Expand All @@ -21,82 +23,100 @@ module Consumer
class MockService

def initialize options = {}
log_description = configure_logger options

@name = options.fetch(:name, "MockService")
pact_dir = options[:pact_dir]
expected_interactions = ExpectedInteractions.new
actual_interactions = ActualInteractions.new
verified_interactions = VerifiedInteractions.new
@consumer_contact_details = {
pact_dir: options[:pact_dir],
consumer: {name: options[:consumer]},
provider: {name: options[:provider]},
interactions: verified_interactions
}

@handlers = [
MissingInteractionsGet.new(@name, @logger, expected_interactions, actual_interactions),
VerificationGet.new(@name, @logger, expected_interactions, actual_interactions, log_description),
InteractionPost.new(@name, @logger, expected_interactions, verified_interactions),
InteractionDelete.new(@name, @logger, expected_interactions, actual_interactions),
LogGet.new(@name, @logger),
PactPost.new(@name, @logger, verified_interactions, pact_dir, options[:consumer_contract_details]),
InteractionReplay.new(@name, @logger, expected_interactions, actual_interactions, verified_interactions)
]
inner_app = InnerApp.new(options)
@app = CorsOriginHeaderMiddleware.new(inner_app, options[:cors_enabled])
end

def call env
response = []
begin
relevant_handler = @handlers.detect { |handler| handler.match? env }
response = relevant_handler.respond(env)
rescue StandardError => e
@logger.error "Error ocurred in mock service: #{e.class} - #{e.message}"
@logger.error e.backtrace.join("\n")
response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
rescue Exception => e
@logger.error "Exception ocurred in mock service: #{e.class} - #{e.message}"
@logger.error e.backtrace.join("\n")
raise e
end
response
@app.call env
end

def shutdown
consumer_contract_writer = ConsumerContractWriter.new(@consumer_contact_details, StdoutLogger.new)
consumer_contract_writer.write if consumer_contract_writer.can_write?
rescue StandardError => e
$stderr.puts "Error writing pact on shutdown. #{e.class} - #{e.message}"
$stderr.puts e.backtrace.join("\n")
@app.shutdown
end

private
class InnerApp

def initialize options = {}
log_description = configure_logger options

def configure_logger options
options = {log_file: $stdout}.merge options
log_stream = options[:log_file]
@logger = Logger.new log_stream
@logger.formatter = options[:log_formatter] if options[:log_formatter]
@logger.level = Pact.configuration.logger.level
@name = options.fetch(:name, "MockService")
pact_dir = options[:pact_dir]
expected_interactions = ExpectedInteractions.new
actual_interactions = ActualInteractions.new
verified_interactions = VerifiedInteractions.new
@consumer_contact_details = {
pact_dir: options[:pact_dir],
consumer: {name: options[:consumer]},
provider: {name: options[:provider]},
interactions: verified_interactions
}

if log_stream.is_a? File
File.absolute_path(log_stream).gsub(Dir.pwd + "/", '')
else
"standard out/err"
@handlers = [
Options.new(@name, @logger, options[:cors_enabled]),
MissingInteractionsGet.new(@name, @logger, expected_interactions, actual_interactions),
VerificationGet.new(@name, @logger, expected_interactions, actual_interactions, log_description),
InteractionPost.new(@name, @logger, expected_interactions, verified_interactions),
InteractionDelete.new(@name, @logger, expected_interactions, actual_interactions),
LogGet.new(@name, @logger),
PactPost.new(@name, @logger, verified_interactions, pact_dir, options[:consumer_contract_details]),
InteractionReplay.new(@name, @logger, expected_interactions, actual_interactions, verified_interactions, options[:cors_enabled])
]
end

def call env
response = []
begin
relevant_handler = @handlers.detect { |handler| handler.match? env }
response = relevant_handler.respond(env)
rescue StandardError => e
@logger.error "Error ocurred in mock service: #{e.class} - #{e.message}"
@logger.error e.backtrace.join("\n")
response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
rescue Exception => e
@logger.error "Exception ocurred in mock service: #{e.class} - #{e.message}"
@logger.error e.backtrace.join("\n")
raise e
end
response
end

def shutdown
write_pact_if_configured
end

private

def write_pact_if_configured
consumer_contract_writer = ConsumerContractWriter.new(@consumer_contact_details, StdoutLogger.new)
consumer_contract_writer.write if consumer_contract_writer.can_write?
end

def configure_logger options
options = {log_file: $stdout}.merge options
log_stream = options[:log_file]
@logger = Logger.new log_stream
@logger.formatter = options[:log_formatter] if options[:log_formatter]
@logger.level = Pact.configuration.logger.level

if log_stream.is_a? File
File.absolute_path(log_stream).gsub(Dir.pwd + "/", '')
else
"standard out/err"
end
end

def to_s
"#{@name} #{super.to_s}"
end
end

def to_s
"#{@name} #{super.to_s}"
end

class StdoutLogger
def info message
$stdout.puts "\n#{message}"
end
end

end
end
end
2 changes: 1 addition & 1 deletion lib/pact/consumer/mock_service/candidate_interactions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ def matching_interactions actual_request

end
end
end
end
30 changes: 30 additions & 0 deletions lib/pact/consumer/mock_service/cors_origin_header_middleware.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Pact
module Consumer
class CorsOriginHeaderMiddleware

def initialize app, cors_enabled
@app = app
@cors_enabled = cors_enabled
end

def call env
response = @app.call env
if env['HTTP_X_PACT_MOCK_SERVICE'] || @cors_enabled
add_cors_header env, response
else
response
end
end

def shutdown
@app.shutdown
end

private

def add_cors_header env, response
[response[0], response[1].merge('Access-Control-Allow-Origin' => env.fetch('HTTP_ORIGIN','*')), response[2]]
end
end
end
end
3 changes: 2 additions & 1 deletion lib/pact/consumer/mock_service/interaction_replay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ class InteractionReplay

attr_accessor :name, :logger, :expected_interactions, :actual_interactions, :verified_interactions

def initialize name, logger, expected_interactions, actual_interactions, verified_interactions
def initialize name, logger, expected_interactions, actual_interactions, verified_interactions, cors_enabled=false
@name = name
@logger = logger
@expected_interactions = expected_interactions
@actual_interactions = actual_interactions
@verified_interactions = verified_interactions
@cors_enabled = cors_enabled
end

def match? env
Expand Down
43 changes: 43 additions & 0 deletions lib/pact/consumer/mock_service/options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'pact/consumer/mock_service/rack_request_helper'
require 'pact/consumer/mock_service/mock_service_administration_endpoint'

module Pact
module Consumer

class Options

include RackRequestHelper

attr_reader :name, :logger, :cors_enabled

def initialize name, logger, cors_enabled
@name = name
@logger = logger
@cors_enabled = cors_enabled
end

def match? env
is_options_request?(env) && (cors_enabled || is_administration_request?(env))
end

def respond env
cors_headers = {
'Access-Control-Allow-Origin' => env.fetch('HTTP_ORIGIN','*'),
'Access-Control-Allow-Headers' => headers_from(env)["Access-Control-Request-Headers"],
'Access-Control-Allow-Methods' => 'DELETE, POST, GET, HEAD, PUT, TRACE, CONNECT'
}
logger.info "Received OPTIONS request for mock service administration endpoint #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']} #{env['PATH_INFO']}. Returning CORS headers: #{cors_headers.to_json}."
[200, cors_headers, []]
end

def is_options_request? env
env['REQUEST_METHOD'] == 'OPTIONS'
end

def is_administration_request? env
env["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"].match(/x-pact-mock-service/i)
end

end
end
end
1 change: 1 addition & 0 deletions lib/pact/consumer/mock_service/rack_request_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'cgi/core'
module Pact
module Consumer

Expand Down
10 changes: 5 additions & 5 deletions lib/pact/consumer_contract/request_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ def to_json(options = {})
end

def as_json options = {}
to_hash
end

def to_hash
hash = {
method: request.method,
path: request.path,
path: path
}
hash[:query] = query if request.specified?(:query)
hash[:headers] = headers if request.specified?(:headers)
Expand All @@ -30,6 +26,10 @@ def to_hash

attr_reader :request

def path
Pact::Reification.from_term(request.path)
end

def headers
Pact::Reification.from_term(request.headers)
end
Expand Down
4 changes: 0 additions & 4 deletions lib/pact/consumer_contract/response_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ def to_json(options = {})
end

def as_json options = {}
to_hash
end

def to_hash
hash = {}
hash[:status] = response.status if response.specified?(:status)
hash[:headers] = response.headers if response.specified?(:headers)
Expand Down
3 changes: 2 additions & 1 deletion lib/pact/mock_service/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ class CLI < Thor
method_option :port, aliases: "-p", desc: "Port on which to run the service"
method_option :ssl, desc: "Use a self-signed SSL cert to run the service over HTTPS"
method_option :log, aliases: "-l", desc: "File to which to log output"
method_option :cors, aliases: "-o", desc: "Support browser security in tests by responding to OPTIONS requests and adding CORS headers to mocked responses"
method_option :pact_dir, aliases: "-d", desc: "Directory to which the pacts will be written"
method_option :consumer, desc: "Consumer name"
method_option :provider, desc: "Provider name"

def service
require 'pact/mock_service/run_standalone'
RunStandalone.call(options)
RunStandalone.(options)
end

desc 'control', "Run a Pact mock service control server."
Expand Down
3 changes: 2 additions & 1 deletion lib/pact/mock_service/run_standalone.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def service_options
service_options = {
pact_dir: options[:pact_dir],
consumer: options[:consumer],
provider: options[:provider]
provider: options[:provider],
cors_enabled: options[:cors]
}
service_options[:log_file] = open_log_file if options[:log]
service_options
Expand Down
2 changes: 1 addition & 1 deletion lib/pact/mock_service/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Pact
module MockService
VERSION = "0.2.3.pre.rc1"
VERSION = "0.2.3"
end
end
2 changes: 1 addition & 1 deletion packaging/wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -e

# Figure out where this script is located.
SELFDIR="`dirname \"$0\"`"
SELFDIR="`cd \"$SELFDIR\" && pwd`"
SELFDIR="`cd \"$SELFDIR\" && cd .. && pwd`"

# Tell Bundler where the Gemfile and gems are.
export BUNDLE_GEMFILE="$SELFDIR/lib/vendor/Gemfile"
Expand Down
Loading

0 comments on commit 09425ac

Please sign in to comment.