pact-mock_service 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +29 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +100 -0
- data/LICENSE.txt +22 -0
- data/README.md +314 -0
- data/Rakefile +6 -0
- data/bin/pact-mock-service +3 -0
- data/lib/pact/consumer/app_manager.rb +159 -0
- data/lib/pact/consumer/interactions_filter.rb +48 -0
- data/lib/pact/consumer/mock_service/app.rb +84 -0
- data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
- data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
- data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
- data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
- data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
- data/lib/pact/consumer/mock_service/log_get.rb +28 -0
- data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
- data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
- data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
- data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
- data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
- data/lib/pact/consumer/mock_service.rb +2 -0
- data/lib/pact/consumer/mock_service_client.rb +65 -0
- data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
- data/lib/pact/consumer/request.rb +27 -0
- data/lib/pact/consumer/server.rb +90 -0
- data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
- data/lib/pact/mock_service/cli.rb +49 -0
- data/lib/pact/mock_service/version.rb +5 -0
- data/lib/pact/mock_service.rb +1 -0
- data/pact-mock-service.gemspec +41 -0
- data/spec/lib/pact/consumer/app_manager_spec.rb +42 -0
- data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
- data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
- data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
- data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
- data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
- data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
- data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
- data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
- data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
- data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/a_consumer-a_producer.json +32 -0
- data/spec/support/a_consumer-a_provider.json +32 -0
- data/spec/support/active_support_if_configured.rb +6 -0
- data/spec/support/app_for_config_ru.rb +4 -0
- data/spec/support/case-insensitive-response-header-matching.json +21 -0
- data/spec/support/case-insensitive-response-header-matching.rb +15 -0
- data/spec/support/consumer_contract_template.json +24 -0
- data/spec/support/dsl_spec_support.rb +7 -0
- data/spec/support/factories.rb +82 -0
- data/spec/support/generated_index.md +4 -0
- data/spec/support/generated_markdown.md +55 -0
- data/spec/support/interaction_view_model.json +63 -0
- data/spec/support/interaction_view_model_with_terms.json +50 -0
- data/spec/support/markdown_pact.json +48 -0
- data/spec/support/missing_provider_states_output.txt +25 -0
- data/spec/support/options.json +21 -0
- data/spec/support/options_app.rb +15 -0
- data/spec/support/pact_helper.rb +57 -0
- data/spec/support/shared_examples_for_request.rb +94 -0
- data/spec/support/spec_support.rb +20 -0
- data/spec/support/stubbing.json +22 -0
- data/spec/support/stubbing_using_allow.rb +29 -0
- data/spec/support/term.json +48 -0
- data/spec/support/test_app_fail.json +61 -0
- data/spec/support/test_app_pass.json +38 -0
- data/spec/support/test_app_with_right_content_type_differ.json +23 -0
- data/tasks/spec.rake +6 -0
- metadata +388 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pact/consumer/mock_service/mock_service_administration_endpoint'
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module Consumer
|
5
|
+
class VerificationGet < MockServiceAdministrationEndpoint
|
6
|
+
|
7
|
+
include RackRequestHelper
|
8
|
+
attr_accessor :interaction_list, :log_description
|
9
|
+
|
10
|
+
def initialize name, logger, interaction_list, log_description
|
11
|
+
super name, logger
|
12
|
+
@interaction_list = interaction_list
|
13
|
+
@log_description = log_description
|
14
|
+
end
|
15
|
+
|
16
|
+
def request_path
|
17
|
+
'/interactions/verification'
|
18
|
+
end
|
19
|
+
|
20
|
+
def request_method
|
21
|
+
'GET'
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond env
|
25
|
+
if interaction_list.all_matched?
|
26
|
+
logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
|
27
|
+
[200, {'Content-Type' => 'text/plain'}, ['Interactions matched']]
|
28
|
+
else
|
29
|
+
error_message = FailureMessage.new(interaction_list).to_s
|
30
|
+
logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". \n#{error_message}"
|
31
|
+
logger.warn error_message
|
32
|
+
[500, {'Content-Type' => 'text/plain'}, ["Actual interactions do not match expected interactions for mock #{name}.\n\n#{error_message}See #{log_description} for details."]]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def example_description env
|
37
|
+
params_hash(env)['example_description']
|
38
|
+
end
|
39
|
+
|
40
|
+
class FailureMessage
|
41
|
+
|
42
|
+
def initialize interaction_list
|
43
|
+
@interaction_list = interaction_list
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
titles_and_summaries.collect do | title, summaries |
|
48
|
+
"#{title}:\n\t#{summaries.join("\n\t")}\n\n" if summaries.any?
|
49
|
+
end.compact.join
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :interaction_list
|
56
|
+
|
57
|
+
def titles_and_summaries
|
58
|
+
{
|
59
|
+
"Incorrect requests" => interaction_list.interaction_mismatches_summaries,
|
60
|
+
"Missing requests" => interaction_list.missing_interactions_summaries,
|
61
|
+
"Unexpected requests" => interaction_list.unexpected_requests_summaries,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'pact/consumer/mock_service_interaction_expectation'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
class MockServiceClient
|
7
|
+
|
8
|
+
MOCK_SERVICE_ADMINISTRATON_HEADERS = {'X-Pact-Mock-Service' => 'true'}
|
9
|
+
|
10
|
+
def initialize port
|
11
|
+
@http = Net::HTTP.new('localhost', port)
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify example_description
|
15
|
+
response = http.request_get("/interactions/verification?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
16
|
+
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
17
|
+
end
|
18
|
+
|
19
|
+
def log msg
|
20
|
+
http.request_get("/log?msg=#{URI.encode(msg)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_interactions wait_max_seconds, poll_interval
|
24
|
+
wait_until_true(wait_max_seconds, poll_interval) do
|
25
|
+
response = http.request_get("/interactions/missing", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
26
|
+
JSON.parse(response.body)['size'] == 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_interactions example_description
|
31
|
+
http.delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_expected_interaction interaction
|
35
|
+
response = http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
|
36
|
+
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.clear_interactions port, example_description
|
40
|
+
Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_pact pacticipant_details
|
44
|
+
response = http.request_post("/pact", pacticipant_details.to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
|
45
|
+
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
46
|
+
response.body
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :http
|
52
|
+
|
53
|
+
#todo: in need a better home (where can we move it?)
|
54
|
+
def wait_until_true timeout=3, interval=0.1
|
55
|
+
time_limit = Time.now + timeout
|
56
|
+
loop do
|
57
|
+
result = yield
|
58
|
+
return if result || Time.now >= time_limit
|
59
|
+
sleep interval
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pact/reification'
|
2
|
+
|
3
|
+
# Represents the Interaction in the form required by the MockService
|
4
|
+
# The json generated will be posted to the MockService to register the expectation
|
5
|
+
module Pact
|
6
|
+
module Consumer
|
7
|
+
class MockServiceInteractionExpectation
|
8
|
+
|
9
|
+
|
10
|
+
def initialize interaction
|
11
|
+
@interaction = interaction
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
hash = {:description => interaction.description}
|
16
|
+
hash[:provider_state] = interaction.provider_state if interaction.provider_state
|
17
|
+
options = interaction.request.options.empty? ? {} : { options: interaction.request.options}
|
18
|
+
hash[:request] = interaction.request.as_json.merge(options)
|
19
|
+
hash[:response] = interaction.response
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json options = {}
|
24
|
+
to_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_json opts = {}
|
28
|
+
as_json.to_json(opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :interaction
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'pact/shared/request'
|
2
|
+
require 'pact/shared/key_not_found'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
module Request
|
7
|
+
class Actual < Pact::Request::Base
|
8
|
+
|
9
|
+
def self.from_hash(hash)
|
10
|
+
sym_hash = symbolize_keys hash
|
11
|
+
method = sym_hash.fetch(:method)
|
12
|
+
path = sym_hash.fetch(:path)
|
13
|
+
query = sym_hash.fetch(:query)
|
14
|
+
headers = sym_hash.fetch(:headers)
|
15
|
+
body = sym_hash.fetch(:body, nil)
|
16
|
+
new(method, path, headers, body, query)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def self.key_not_found
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
# Copied shamelessly from Capybara
|
6
|
+
module Pact
|
7
|
+
class Server
|
8
|
+
class Middleware
|
9
|
+
attr_accessor :error
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if env["PATH_INFO"] == "/__identify__"
|
17
|
+
[200, {}, [@app.object_id.to_s]]
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
@app.call(env)
|
21
|
+
rescue StandardError => e
|
22
|
+
@error = e unless @error
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def ports
|
31
|
+
@ports ||= {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :app, :port
|
36
|
+
|
37
|
+
def initialize(app, port)
|
38
|
+
@app = app
|
39
|
+
@middleware = Middleware.new(@app)
|
40
|
+
@server_thread = nil # supress warnings
|
41
|
+
@port = port
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_error!
|
45
|
+
@middleware.error = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def error
|
49
|
+
@middleware.error
|
50
|
+
end
|
51
|
+
|
52
|
+
def host
|
53
|
+
"localhost"
|
54
|
+
end
|
55
|
+
|
56
|
+
def responsive?
|
57
|
+
return false if @server_thread && @server_thread.join(0)
|
58
|
+
|
59
|
+
res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
|
60
|
+
|
61
|
+
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
|
62
|
+
return res.body == @app.object_id.to_s
|
63
|
+
end
|
64
|
+
rescue SystemCallError
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_default_server(app, port)
|
69
|
+
require 'rack/handler/webrick'
|
70
|
+
Rack::Handler::WEBrick.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
|
71
|
+
end
|
72
|
+
|
73
|
+
def boot
|
74
|
+
unless responsive?
|
75
|
+
Pact::Server.ports[@app.object_id] = @port
|
76
|
+
|
77
|
+
@server_thread = Thread.new do
|
78
|
+
run_default_server(@middleware, @port)
|
79
|
+
end
|
80
|
+
|
81
|
+
Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
|
82
|
+
end
|
83
|
+
rescue Timeout::Error
|
84
|
+
raise "Rack application timed out during boot"
|
85
|
+
else
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'pact/consumer_contract'
|
2
|
+
require 'pact/consumer/interactions_filter'
|
3
|
+
require 'pact/consumer_contract/file_name'
|
4
|
+
|
5
|
+
module Pact
|
6
|
+
|
7
|
+
class ConsumerContractWriter
|
8
|
+
|
9
|
+
attr_reader :consumer_contract_details, :pactfile_write_mode, :interactions, :logger
|
10
|
+
|
11
|
+
def initialize consumer_contract_details, logger
|
12
|
+
@logger = logger
|
13
|
+
@consumer_contract_details = consumer_contract_details
|
14
|
+
@pactfile_write_mode = consumer_contract_details.fetch(:pactfile_write_mode, :overwrite).to_sym
|
15
|
+
@interactions = consumer_contract_details.fetch(:interactions)
|
16
|
+
end
|
17
|
+
|
18
|
+
def consumer_contract
|
19
|
+
@consumer_contract ||= Pact::ConsumerContract.new(
|
20
|
+
consumer: ServiceConsumer.new(name: consumer_contract_details[:consumer][:name]),
|
21
|
+
provider: ServiceProvider.new(name: consumer_contract_details[:provider][:name]),
|
22
|
+
interactions: interactions_for_new_consumer_contract)
|
23
|
+
end
|
24
|
+
|
25
|
+
def write
|
26
|
+
consumer_contract.update_pactfile
|
27
|
+
consumer_contract.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def interactions_for_new_consumer_contract
|
31
|
+
if pactfile_write_mode == :update
|
32
|
+
merged_interactions = existing_interactions
|
33
|
+
filter = Consumer::UpdatableInteractionsFilter.new(merged_interactions)
|
34
|
+
interactions.each {|i| filter << i }
|
35
|
+
merged_interactions
|
36
|
+
else
|
37
|
+
interactions
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def existing_interactions
|
42
|
+
interactions = []
|
43
|
+
if pactfile_exists?
|
44
|
+
begin
|
45
|
+
interactions = existing_consumer_contract.interactions
|
46
|
+
info_and_puts "*****************************************************************************"
|
47
|
+
info_and_puts "Updating existing file .#{pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
|
48
|
+
info_and_puts "Only interactions defined in this test run will be updated."
|
49
|
+
info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
|
50
|
+
info_and_puts "*****************************************************************************"
|
51
|
+
rescue StandardError => e
|
52
|
+
warn_and_stderr "Could not load existing consumer contract from #{pactfile_path} due to #{e}"
|
53
|
+
logger.error e
|
54
|
+
logger.error e.backtrace
|
55
|
+
warn_and_stderr "Creating a new file."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
interactions
|
59
|
+
end
|
60
|
+
|
61
|
+
def pactfile_exists?
|
62
|
+
File.exist?(pactfile_path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def pactfile_path
|
66
|
+
Pact::FileName.file_path consumer_contract_details[:consumer][:name], consumer_contract_details[:provider][:name]
|
67
|
+
end
|
68
|
+
|
69
|
+
def existing_consumer_contract
|
70
|
+
Pact::ConsumerContract.from_uri(pactfile_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def warn_and_stderr msg
|
74
|
+
Pact.configuration.error_stream.puts msg
|
75
|
+
logger.warn msg
|
76
|
+
end
|
77
|
+
|
78
|
+
def info_and_puts msg
|
79
|
+
$stdout.puts msg
|
80
|
+
logger.info msg
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'find_a_port'
|
2
|
+
require 'thor'
|
3
|
+
require 'thwait'
|
4
|
+
require 'rack/handler/webrick'
|
5
|
+
|
6
|
+
# TODO rename CLI so as not to clash
|
7
|
+
|
8
|
+
module Pact
|
9
|
+
module MockService
|
10
|
+
class CLI < Thor
|
11
|
+
|
12
|
+
desc 'service', "Start a mock service"
|
13
|
+
method_option :port, aliases: "-p", desc: "Port on which to run the service"
|
14
|
+
method_option :log, aliases: "-l", desc: "File to which to log output"
|
15
|
+
method_option :quiet, aliases: "-q", desc: "If true, no admin messages will be shown"
|
16
|
+
|
17
|
+
def service
|
18
|
+
RunStandaloneMockService.call(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
default_task :service
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def log message
|
26
|
+
puts message unless options[:quiet]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class RunStandaloneMockService
|
31
|
+
|
32
|
+
def self.call options
|
33
|
+
require 'pact/consumer/mock_service/app'
|
34
|
+
service_options = {}
|
35
|
+
if options[:log]
|
36
|
+
log = File.open(options[:log], 'w')
|
37
|
+
log.sync = true
|
38
|
+
service_options[:log_file] = log
|
39
|
+
end
|
40
|
+
|
41
|
+
port = options[:port] || FindAPort.available_port
|
42
|
+
mock_service = Pact::Consumer::MockService.new(service_options)
|
43
|
+
trap(:INT) { Rack::Handler::WEBrick.shutdown }
|
44
|
+
Rack::Handler::WEBrick.run(mock_service, :Port => port, :AccessLog => [])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'pact/consumer/mock_service'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pact/mock_service/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pact-mock_service"
|
8
|
+
gem.version = Pact::MockService::VERSION
|
9
|
+
gem.authors = ["James Fraser", "Sergei Matheson", "Brent Snook", "Ronald Holshausen", "Beth Skurrie"]
|
10
|
+
gem.email = ["james.fraser@alumni.swinburne.edu", "sergei.matheson@gmail.com", "brent@fuglylogic.com", "uglyog@gmail.com", "bskurrie@dius.com.au"]
|
11
|
+
gem.summary = %q{Provides a mock service for use with Pact}
|
12
|
+
gem.homepage = "https://github.com/bethesque/pact-mock_service"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.license = 'MIT'
|
19
|
+
|
20
|
+
gem.add_runtime_dependency 'rack'
|
21
|
+
gem.add_runtime_dependency 'rspec', '>=2.14'
|
22
|
+
gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
|
23
|
+
gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
|
24
|
+
gem.add_runtime_dependency 'awesome_print', '~> 1.1'
|
25
|
+
gem.add_runtime_dependency 'thor'
|
26
|
+
gem.add_runtime_dependency 'json' #Not locking down a version because buncher gem requires 1.6, while other projects use 1.7.
|
27
|
+
gem.add_runtime_dependency 'webrick'
|
28
|
+
gem.add_runtime_dependency 'term-ansicolor', '~> 1.0'
|
29
|
+
|
30
|
+
unless ENV['X_PACT_DEVELOPMENT']
|
31
|
+
gem.add_runtime_dependency 'pact-support', '~> 0.0.1'
|
32
|
+
end
|
33
|
+
|
34
|
+
gem.add_development_dependency 'rake', '~> 10.0.3'
|
35
|
+
gem.add_development_dependency 'webmock', '~> 1.18.0'
|
36
|
+
gem.add_development_dependency 'pry'
|
37
|
+
gem.add_development_dependency 'fakefs', '~> 0.4'
|
38
|
+
gem.add_development_dependency 'hashie', '~> 2.0'
|
39
|
+
gem.add_development_dependency 'activesupport'
|
40
|
+
gem.add_development_dependency 'faraday'
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pact/consumer/app_manager'
|
3
|
+
|
4
|
+
module Pact::Consumer
|
5
|
+
describe AppManager do
|
6
|
+
before do
|
7
|
+
AppManager.instance.clear_all
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "start_service_for" do
|
11
|
+
before do
|
12
|
+
allow_any_instance_of(AppRegistration).to receive(:spawn) # Don't want process actually spawning during the tests
|
13
|
+
end
|
14
|
+
let(:name) { 'some_service'}
|
15
|
+
context "for http://localhost" do
|
16
|
+
let(:url) { 'http://localhost:1234'}
|
17
|
+
it "starts a mock service at the given port on localhost" do
|
18
|
+
expect_any_instance_of(AppRegistration).to receive(:spawn)
|
19
|
+
AppManager.instance.register_mock_service_for name, url
|
20
|
+
AppManager.instance.spawn_all
|
21
|
+
end
|
22
|
+
|
23
|
+
it "registers the mock service as running on the given port" do
|
24
|
+
AppManager.instance.register_mock_service_for name, url
|
25
|
+
expect(AppManager.instance.app_registered_on?(1234)).to eq true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
context "for https://" do
|
29
|
+
let(:url) { 'https://localhost:1234'}
|
30
|
+
it "should throw an unsupported error" do
|
31
|
+
expect { AppManager.instance.register_mock_service_for name, url }.to raise_error "Currently only http is supported"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "for a host other than localhost" do
|
35
|
+
let(:url) { 'http://aserver:1234'}
|
36
|
+
it "should throw an unsupported error" do
|
37
|
+
expect { AppManager.instance.register_mock_service_for name, url }.to raise_error "Currently only services on localhost are supported"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Pact
|
6
|
+
module Consumer
|
7
|
+
|
8
|
+
describe MockService do
|
9
|
+
|
10
|
+
include Rack::Test::Methods
|
11
|
+
|
12
|
+
def app
|
13
|
+
MockService.new(log_file: temp_file)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:temp_file) { Tempfile.new('log') }
|
17
|
+
|
18
|
+
after do
|
19
|
+
temp_file.close
|
20
|
+
temp_file.unlink
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when a StandardError is encountered" do
|
24
|
+
let(:response) { JSON.parse(last_response.body)}
|
25
|
+
let(:interaction_replay) { double(InteractionReplay, :match? => true)}
|
26
|
+
|
27
|
+
before do
|
28
|
+
expect(InteractionReplay).to receive(:new).and_return(interaction_replay)
|
29
|
+
expect(interaction_replay).to receive(:respond).and_raise("an error")
|
30
|
+
end
|
31
|
+
|
32
|
+
subject { get "/" }
|
33
|
+
|
34
|
+
it "returns a json error" do
|
35
|
+
subject
|
36
|
+
expect(last_response.content_type).to eq 'application/json'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "includes the error message" do
|
40
|
+
subject
|
41
|
+
expect(response['message']).to eq "an error"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "includes the backtrace" do
|
45
|
+
subject
|
46
|
+
expect(response['backtrace']).to be_instance_of Array
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pact/consumer/mock_service/interaction_list'
|
3
|
+
|
4
|
+
module Pact::Consumer
|
5
|
+
|
6
|
+
describe InteractionList do
|
7
|
+
shared_context "unexpected requests and missed interactions" do
|
8
|
+
let(:expected_interaction) { InteractionFactory.create }
|
9
|
+
let(:unexpected_request) { RequestFactory.create_actual method: 'put' }
|
10
|
+
let(:candidate_interaction) { double("Pact::Interaction") }
|
11
|
+
let(:candidate_interactions) { [candidate_interaction] }
|
12
|
+
let(:interaction_mismatch) { instance_double("Pact::Consumer::InteractionMismatch", :short_summary => 'blah', :candidate_interactions => candidate_interactions)}
|
13
|
+
subject {
|
14
|
+
interactionList = InteractionList.new
|
15
|
+
interactionList.add expected_interaction
|
16
|
+
interactionList.register_unexpected_request unexpected_request
|
17
|
+
interactionList.register_interaction_mismatch interaction_mismatch
|
18
|
+
interactionList
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
shared_context "no unexpected requests or missed interactions exist" do
|
23
|
+
let(:expected_interaction) { InteractionFactory.create }
|
24
|
+
subject {
|
25
|
+
interactionList = InteractionList.new
|
26
|
+
interactionList.add expected_interaction
|
27
|
+
interactionList.register_matched expected_interaction
|
28
|
+
interactionList
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "interaction_diffs" do
|
33
|
+
context "when unexpected requests and missed interactions exist" do
|
34
|
+
include_context "unexpected requests and missed interactions"
|
35
|
+
let(:expected_diff) {
|
36
|
+
{:missing_interactions=>["GET /path"],
|
37
|
+
:unexpected_requests=>["PUT /path?query"],
|
38
|
+
:interaction_mismatches => ['blah']}
|
39
|
+
}
|
40
|
+
it "returns the unexpected requests and missed interactions" do
|
41
|
+
expect(subject.interaction_diffs).to eq expected_diff
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when no unexpected requests or missed interactions exist" do
|
46
|
+
include_context "no unexpected requests or missed interactions exist"
|
47
|
+
let(:expected_diff) {
|
48
|
+
{}
|
49
|
+
}
|
50
|
+
it "returns an empty hash" do
|
51
|
+
expect(subject.interaction_diffs).to eq expected_diff
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "all_matched?" do
|
57
|
+
context "when unexpected requests or missed interactions exist" do
|
58
|
+
include_context "unexpected requests and missed interactions"
|
59
|
+
it "returns false" do
|
60
|
+
expect(subject.all_matched?).to be false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
context "when unexpected requests or missed interactions do not exist" do
|
64
|
+
include_context "no unexpected requests or missed interactions exist"
|
65
|
+
it "returns false" do
|
66
|
+
expect(subject.all_matched?).to be true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "missing_interactions_summaries" do
|
72
|
+
include_context "unexpected requests and missed interactions"
|
73
|
+
it "returns a list of the method and paths for each missing interaction" do
|
74
|
+
expect(subject.missing_interactions_summaries).to eq ["GET /path"]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|