pact-mock_service 0.0.1

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.
Files changed (75) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +100 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +314 -0
  10. data/Rakefile +6 -0
  11. data/bin/pact-mock-service +3 -0
  12. data/lib/pact/consumer/app_manager.rb +159 -0
  13. data/lib/pact/consumer/interactions_filter.rb +48 -0
  14. data/lib/pact/consumer/mock_service/app.rb +84 -0
  15. data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
  16. data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
  17. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
  18. data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
  19. data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
  20. data/lib/pact/consumer/mock_service/log_get.rb +28 -0
  21. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
  22. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
  23. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  24. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  25. data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
  26. data/lib/pact/consumer/mock_service.rb +2 -0
  27. data/lib/pact/consumer/mock_service_client.rb +65 -0
  28. data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
  29. data/lib/pact/consumer/request.rb +27 -0
  30. data/lib/pact/consumer/server.rb +90 -0
  31. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  32. data/lib/pact/mock_service/cli.rb +49 -0
  33. data/lib/pact/mock_service/version.rb +5 -0
  34. data/lib/pact/mock_service.rb +1 -0
  35. data/pact-mock-service.gemspec +41 -0
  36. data/spec/lib/pact/consumer/app_manager_spec.rb +42 -0
  37. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  38. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
  39. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
  40. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  41. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
  42. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
  43. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  44. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  45. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  46. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/spec/support/a_consumer-a_producer.json +32 -0
  49. data/spec/support/a_consumer-a_provider.json +32 -0
  50. data/spec/support/active_support_if_configured.rb +6 -0
  51. data/spec/support/app_for_config_ru.rb +4 -0
  52. data/spec/support/case-insensitive-response-header-matching.json +21 -0
  53. data/spec/support/case-insensitive-response-header-matching.rb +15 -0
  54. data/spec/support/consumer_contract_template.json +24 -0
  55. data/spec/support/dsl_spec_support.rb +7 -0
  56. data/spec/support/factories.rb +82 -0
  57. data/spec/support/generated_index.md +4 -0
  58. data/spec/support/generated_markdown.md +55 -0
  59. data/spec/support/interaction_view_model.json +63 -0
  60. data/spec/support/interaction_view_model_with_terms.json +50 -0
  61. data/spec/support/markdown_pact.json +48 -0
  62. data/spec/support/missing_provider_states_output.txt +25 -0
  63. data/spec/support/options.json +21 -0
  64. data/spec/support/options_app.rb +15 -0
  65. data/spec/support/pact_helper.rb +57 -0
  66. data/spec/support/shared_examples_for_request.rb +94 -0
  67. data/spec/support/spec_support.rb +20 -0
  68. data/spec/support/stubbing.json +22 -0
  69. data/spec/support/stubbing_using_allow.rb +29 -0
  70. data/spec/support/term.json +48 -0
  71. data/spec/support/test_app_fail.json +61 -0
  72. data/spec/support/test_app_pass.json +38 -0
  73. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  74. data/tasks/spec.rake +6 -0
  75. metadata +388 -0
@@ -0,0 +1,48 @@
1
+ #
2
+ # When running in pactfile_write_mode :overwrite, all interactions are cleared from the
3
+ # pact file, and all new interactions should be distinct (unique description and provider state).
4
+ # When running in pactfile_write_mode :update, an interaction with the same description
5
+ # and provider state as an existing one will just overwrite that one interaction.
6
+ #
7
+
8
+ module Pact
9
+ module Consumer
10
+
11
+ #TODO: think of a better word than filter
12
+ class InteractionsFilter
13
+ def initialize interactions = []
14
+ @interactions = interactions
15
+ end
16
+
17
+ def index_of interaction
18
+ @interactions.find_index{ |i| i.matches_criteria?(description: interaction.description, provider_state: interaction.provider_state)}
19
+ end
20
+ end
21
+
22
+ class UpdatableInteractionsFilter < InteractionsFilter
23
+
24
+ def << interaction
25
+ if (ndx = index_of(interaction))
26
+ @interactions[ndx] = interaction
27
+ else
28
+ @interactions << interaction
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ class DistinctInteractionsFilter < InteractionsFilter
35
+
36
+ def << interaction
37
+ if (ndx = index_of(interaction))
38
+ if @interactions[ndx] != interaction
39
+ raise "Interaction with same description (#{interaction.description}) and provider state (#{interaction.provider_state}) already exists"
40
+ end
41
+ else
42
+ @interactions << interaction
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,84 @@
1
+ require 'rack'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'logger'
5
+ require 'awesome_print'
6
+ require 'awesome_print/core_ext/logger' #For some reason we get an error indicating that the method 'ap' is private unless we load this specifically
7
+ require 'pact/consumer/request'
8
+ require 'pact/consumer/mock_service/interaction_list'
9
+ require 'pact/consumer/mock_service/interaction_delete'
10
+ require 'pact/consumer/mock_service/interaction_post'
11
+ require 'pact/consumer/mock_service/interaction_replay'
12
+ require 'pact/consumer/mock_service/missing_interactions_get'
13
+ require 'pact/consumer/mock_service/verification_get'
14
+ require 'pact/consumer/mock_service/log_get'
15
+ require 'pact/consumer/mock_service/pact_post'
16
+ require 'pact/support'
17
+
18
+ AwesomePrint.defaults = {
19
+ indent: -2,
20
+ plain: true,
21
+ index: false
22
+ }
23
+
24
+ module Pact
25
+ module Consumer
26
+
27
+ class MockService
28
+
29
+ def initialize options = {}
30
+ log_description = configure_logger options
31
+ interaction_list = InteractionList.new
32
+
33
+ @name = options.fetch(:name, "MockService")
34
+ interactions = []
35
+ @handlers = [
36
+ MissingInteractionsGet.new(@name, @logger, interaction_list),
37
+ VerificationGet.new(@name, @logger, interaction_list, log_description),
38
+ InteractionPost.new(@name, @logger, interaction_list),
39
+ InteractionDelete.new(@name, @logger, interaction_list),
40
+ LogGet.new(@name, @logger),
41
+ PactPost.new(@name, @logger, interactions),
42
+ InteractionReplay.new(@name, @logger, interaction_list, interactions)
43
+ ]
44
+ end
45
+
46
+ def configure_logger options
47
+ options = {log_file: $stdout}.merge options
48
+ log_stream = options[:log_file]
49
+ @logger = Logger.new log_stream
50
+ @logger.level = Pact.configuration.logger.level
51
+
52
+ if log_stream.is_a? File
53
+ File.absolute_path(log_stream).gsub(Dir.pwd + "/", '')
54
+ else
55
+ "standard out/err"
56
+ end
57
+ end
58
+
59
+ def to_s
60
+ "#{@name} #{super.to_s}"
61
+ end
62
+
63
+ def call env
64
+ response = []
65
+ begin
66
+ relevant_handler = @handlers.detect { |handler| handler.match? env }
67
+ response = relevant_handler.respond env
68
+ rescue StandardError => e
69
+ @logger.error 'Error ocurred in mock service:'
70
+ @logger.ap e, :error
71
+ @logger.ap e.backtrace
72
+ response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
73
+ rescue Exception => e
74
+ @logger.error 'Exception ocurred in mock service:'
75
+ @logger.ap e, :error
76
+ @logger.ap e.backtrace
77
+ raise e
78
+ end
79
+ response
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ require 'pact/consumer/mock_service/rack_request_helper'
2
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
3
+
4
+ module Pact
5
+ module Consumer
6
+
7
+ class InteractionDelete < MockServiceAdministrationEndpoint
8
+
9
+ include RackRequestHelper
10
+
11
+ attr_accessor :interaction_list
12
+
13
+ def initialize name, logger, interaction_list
14
+ super name, logger
15
+ @interaction_list = interaction_list
16
+ end
17
+
18
+ def request_path
19
+ '/interactions'
20
+ end
21
+
22
+ def request_method
23
+ 'DELETE'
24
+ end
25
+
26
+ def respond env
27
+ interaction_list.clear
28
+ logger.info "Cleared interactions before example \"#{params_hash(env)['example_description']}\""
29
+ [200, {}, ['Deleted interactions']]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,76 @@
1
+ module Pact
2
+ module Consumer
3
+ class InteractionList
4
+
5
+ attr_reader :interactions
6
+ attr_reader :unexpected_requests
7
+ attr_reader :interaction_mismatches
8
+
9
+ def initialize
10
+ clear
11
+ end
12
+
13
+ # For testing, sigh
14
+ def clear
15
+ @interactions = []
16
+ @matched_interactions = []
17
+ @interaction_mismatches = []
18
+ @unexpected_requests = []
19
+ end
20
+
21
+ def add interactions
22
+ @interactions << interactions
23
+ end
24
+
25
+ def register_matched interaction
26
+ @matched_interactions << interaction
27
+ end
28
+
29
+ def register_unexpected_request request
30
+ @unexpected_requests << request
31
+ end
32
+
33
+ def register_interaction_mismatch interaction_mismatch
34
+ @interaction_mismatches << interaction_mismatch
35
+ end
36
+
37
+ def all_matched?
38
+ interaction_diffs.empty?
39
+ end
40
+
41
+ def missing_interactions
42
+ @interactions - @matched_interactions - @interaction_mismatches.collect(&:candidate_interactions).flatten
43
+ end
44
+
45
+ def missing_interactions_summaries
46
+ missing_interactions.collect(&:request).collect(&:method_and_path)
47
+ end
48
+
49
+ def interaction_mismatches_summaries
50
+ interaction_mismatches.collect(&:short_summary)
51
+ end
52
+
53
+ def unexpected_requests_summaries
54
+ unexpected_requests.collect(&:method_and_path)
55
+ end
56
+
57
+ def interaction_diffs
58
+ {
59
+ :missing_interactions => missing_interactions_summaries,
60
+ :interaction_mismatches => interaction_mismatches_summaries,
61
+ :unexpected_requests => unexpected_requests_summaries
62
+ }.inject({}) do | hash, pair |
63
+ hash[pair.first] = pair.last if pair.last.any?
64
+ hash
65
+ end
66
+ end
67
+
68
+ def find_candidate_interactions actual_request
69
+ interactions.select do | interaction |
70
+ interaction.request.matches_route? actual_request
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,73 @@
1
+ module Pact
2
+ module Consumer
3
+
4
+ # Presents the differences between an actual request, and a list of
5
+ # expected interactions where the methods and paths match the actual request.
6
+ # This is used to display a helpful message to the user when a request
7
+ # comes in that doesn't match any of the expected interactions.
8
+ class InteractionMismatch
9
+
10
+ attr_accessor :candidate_interactions, :actual_request
11
+
12
+ # Assumes the method and path matches...
13
+
14
+ def initialize candidate_interactions, actual_request
15
+ @candidate_interactions = candidate_interactions
16
+ @actual_request = actual_request
17
+ @candiate_diffs = candidate_interactions.collect{ | candidate_interaction| CandidateDiff.new(candidate_interaction, actual_request)}
18
+ end
19
+
20
+ def to_hash
21
+ candiate_diffs.collect(&:to_hash)
22
+ end
23
+
24
+ def to_s
25
+ candiate_diffs.collect(&:to_s).join("\n")
26
+ end
27
+
28
+ def short_summary
29
+ mismatched_attributes = candiate_diffs.collect(&:mismatched_attributes).flatten.uniq.join(", ").reverse.sub(",", "dna ").reverse #OMG what a hack!
30
+ actual_request.method_and_path + " (request #{mismatched_attributes} did not match)"
31
+ end
32
+
33
+ private
34
+
35
+ attr_accessor :candiate_diffs
36
+
37
+ class CandidateDiff
38
+
39
+ attr_accessor :candidate_interaction, :actual_request
40
+
41
+ def initialize candidate_interaction, actual_request
42
+ @candidate_interaction = candidate_interaction
43
+ @actual_request = actual_request
44
+ end
45
+
46
+ def mismatched_attributes
47
+ diff.keys
48
+ end
49
+
50
+ def to_hash
51
+ summary = {:description => candidate_interaction.description}
52
+ summary[:provider_state] = candidate_interaction.provider_state if candidate_interaction.provider_state
53
+ summary.merge(diff)
54
+ end
55
+
56
+ def to_s
57
+ [
58
+ "Diff with interaction: #{candidate_interaction.description_with_provider_state_quoted}",
59
+ diff_formatter.call(diff, {colour: false})
60
+ ].join("\n")
61
+ end
62
+
63
+ def diff_formatter
64
+ Pact.configuration.diff_formatter_for_content_type(candidate_interaction.request.content_type)
65
+ end
66
+
67
+ def diff
68
+ @diff ||= candidate_interaction.request.difference(actual_request)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+
3
+ module Pact
4
+ module Consumer
5
+ class InteractionPost < MockServiceAdministrationEndpoint
6
+
7
+ attr_accessor :interaction_list
8
+
9
+ def initialize name, logger, interaction_list
10
+ super name, logger
11
+ @interaction_list = interaction_list
12
+ end
13
+
14
+ def request_path
15
+ '/interactions'
16
+ end
17
+
18
+ def request_method
19
+ 'POST'
20
+ end
21
+
22
+ def respond env
23
+ interaction = Interaction.from_hash(JSON.load(env['rack.input'].string))
24
+ interaction_list.add interaction
25
+ logger.info "Registered expected interaction #{interaction.request.method_and_path}"
26
+ logger.debug JSON.pretty_generate JSON.parse(interaction.to_json)
27
+ [200, {}, ['Added interaction']]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,139 @@
1
+ require 'pact/matchers'
2
+ require 'pact/consumer/mock_service/rack_request_helper'
3
+ require 'pact/consumer/mock_service/interaction_mismatch'
4
+ require 'pact/consumer_contract'
5
+ require 'pact/consumer/interactions_filter'
6
+
7
+ module Pact
8
+ module Consumer
9
+
10
+ class InteractionReplay
11
+ include Pact::Matchers
12
+ include RackRequestHelper
13
+
14
+ attr_accessor :name, :logger, :interaction_list, :interactions
15
+
16
+ def initialize name, logger, interaction_list, interactions
17
+ @name = name
18
+ @logger = logger
19
+ @interaction_list = interaction_list
20
+ @interactions = DistinctInteractionsFilter.new(interactions)
21
+ end
22
+
23
+ def match? env
24
+ true # default handler
25
+ end
26
+
27
+ def respond env
28
+ find_response request_as_hash_from(env)
29
+ end
30
+
31
+ private
32
+
33
+ def add_verified_interaction interaction
34
+ interactions << interaction
35
+ end
36
+
37
+ def find_response request_hash
38
+ actual_request = Request::Actual.from_hash(request_hash)
39
+ logger.info "Received request #{actual_request.method_and_path}"
40
+ logger.debug pretty_generate actual_request
41
+ candidate_interactions = interaction_list.find_candidate_interactions actual_request
42
+ matching_interactions = find_matching_interactions actual_request, from: candidate_interactions
43
+
44
+ case matching_interactions.size
45
+ when 0 then handle_unrecognised_request actual_request, candidate_interactions
46
+ when 1 then handle_matched_interaction matching_interactions.first
47
+ else
48
+ handle_more_than_one_matching_interaction actual_request, matching_interactions
49
+ end
50
+ end
51
+
52
+ def find_matching_interactions actual_request, opts
53
+ candidate_interactions = opts.fetch(:from)
54
+ candidate_interactions.select do | candidate_interaction |
55
+ candidate_interaction.request.matches? actual_request
56
+ end
57
+ end
58
+
59
+ def handle_matched_interaction interaction
60
+ interaction_list.register_matched interaction
61
+ add_verified_interaction interaction
62
+ response = response_from(interaction.response)
63
+ logger.info "Found matching response for #{interaction.request.method_and_path}"
64
+ logger.debug pretty_generate(interaction.response)
65
+ response
66
+ end
67
+
68
+ def multiple_interactions_found_response actual_request, matching_interactions
69
+ response = {
70
+ message: "Multiple interaction found for #{actual_request.method_and_path}",
71
+ matching_interactions: matching_interactions.collect{ | interaction | request_summary_for(interaction) }
72
+ }
73
+ [500, {'Content-Type' => 'application/json'}, [response.to_json]]
74
+ end
75
+
76
+ def handle_more_than_one_matching_interaction actual_request, matching_interactions
77
+ logger.error "Multiple interactions found for #{actual_request.method_and_path}:"
78
+ matching_interactions.each do | interaction |
79
+ logger.debug pretty_generate(interaction)
80
+ end
81
+ multiple_interactions_found_response actual_request, matching_interactions
82
+ end
83
+
84
+ def interaction_mismatch actual_request, candidate_interactions
85
+ InteractionMismatch.new(candidate_interactions, actual_request)
86
+ end
87
+
88
+ def request_summary_for interaction
89
+ summary = {:description => interaction.description}
90
+ summary[:provider_state] if interaction.provider_state
91
+ summary[:request] = interaction.request
92
+ summary
93
+ end
94
+
95
+ def unrecognised_request_response interaction_mismatch
96
+ response = {
97
+ message: "No interaction found for #{interaction_mismatch.actual_request.method_and_path}",
98
+ interaction_diffs: interaction_mismatch.to_hash
99
+ }
100
+ [500, {'Content-Type' => 'application/json'}, [response.to_json]]
101
+ end
102
+
103
+ def log_unrecognised_request_and_interaction_diff interaction_mismatch
104
+ logger.error "No matching interaction found on #{name} for #{interaction_mismatch.actual_request.method_and_path}"
105
+ logger.error 'Interaction diffs for that route:'
106
+ logger.error(interaction_mismatch.to_s)
107
+ end
108
+
109
+ def handle_unrecognised_request actual_request, candidate_interactions
110
+ interaction_mismatch = interaction_mismatch(actual_request, candidate_interactions)
111
+ if candidate_interactions.any?
112
+ interaction_list.register_interaction_mismatch interaction_mismatch
113
+ else
114
+ interaction_list.register_unexpected_request actual_request
115
+ end
116
+ log_unrecognised_request_and_interaction_diff interaction_mismatch
117
+ unrecognised_request_response interaction_mismatch
118
+ end
119
+
120
+ def response_from response
121
+ [response.status, (response.headers || {}).to_hash, [render_body(Pact::Reification.from_term(response.body))]]
122
+ end
123
+
124
+ def render_body body
125
+ return '' unless body
126
+ body.kind_of?(String) ? body.force_encoding('utf-8') : body.to_json
127
+ end
128
+
129
+ def logger_info_ap msg
130
+ logger.info msg
131
+ end
132
+
133
+ #Doesn't seem to reliably pretty generate unless we go to JSON and back again :(
134
+ def pretty_generate object
135
+ JSON.pretty_generate(JSON.parse(object.to_json))
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,28 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+
3
+ module Pact
4
+ module Consumer
5
+ class LogGet < MockServiceAdministrationEndpoint
6
+
7
+ include RackRequestHelper
8
+
9
+ def request_path
10
+ '/log'
11
+ end
12
+
13
+ def request_method
14
+ 'GET'
15
+ end
16
+
17
+
18
+ def respond env
19
+ logger.info "Debug message from client - #{message(env)}"
20
+ [200, {}, []]
21
+ end
22
+
23
+ def message env
24
+ params_hash(env)['msg']
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+
3
+ module Pact
4
+ module Consumer
5
+
6
+ class MissingInteractionsGet < MockServiceAdministrationEndpoint
7
+ include RackRequestHelper
8
+
9
+ def initialize name, logger, interaction_list
10
+ super name, logger
11
+ @interaction_list = interaction_list
12
+ end
13
+
14
+ def request_path
15
+ '/interactions/missing'
16
+ end
17
+
18
+ def request_method
19
+ 'GET'
20
+ end
21
+
22
+ def respond env
23
+ number_of_missing_interactions = @interaction_list.missing_interactions.size
24
+ logger.info "Number of missing interactions for mock \"#{name}\" = #{number_of_missing_interactions}"
25
+ [200, {}, [{size: number_of_missing_interactions}.to_json]]
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ require 'pact/consumer/mock_service/rack_request_helper'
2
+ module Pact
3
+ module Consumer
4
+ class MockServiceAdministrationEndpoint
5
+
6
+ attr_accessor :logger, :name
7
+
8
+ def initialize name, logger
9
+ @name = name
10
+ @logger = logger
11
+ end
12
+
13
+ include RackRequestHelper
14
+
15
+ def match? env
16
+ headers_from(env)['X-Pact-Mock-Service'] &&
17
+ env['REQUEST_PATH'] == request_path &&
18
+ env['REQUEST_METHOD'] == request_method
19
+ end
20
+
21
+ def request_path
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def request_method
26
+ raise NotImplementedError
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+ require 'pact/consumer_contract/consumer_contract_writer'
3
+
4
+ module Pact
5
+ module Consumer
6
+ class PactPost < MockServiceAdministrationEndpoint
7
+
8
+ attr_accessor :consumer_contract, :interactions
9
+
10
+ def initialize name, logger, interactions
11
+ super name, logger
12
+ @interactions = interactions
13
+ end
14
+
15
+ def request_path
16
+ '/pact'
17
+ end
18
+
19
+ def request_method
20
+ 'POST'
21
+ end
22
+
23
+ def respond env
24
+ consumer_contract_details = JSON.parse(env['rack.input'].string, symbolize_names: true)
25
+ logger.info "Writing pact with details #{consumer_contract_details}"
26
+ consumer_contract_writer = ConsumerContractWriter.new(consumer_contract_details.merge(interactions: interactions), logger)
27
+ json = consumer_contract_writer.write
28
+
29
+ [200, {'Content-Type' =>'application/json'}, [json]]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module Pact
2
+ module Consumer
3
+
4
+ module RackRequestHelper
5
+ REQUEST_KEYS = {
6
+ 'REQUEST_METHOD' => :method,
7
+ 'PATH_INFO' => :path,
8
+ 'QUERY_STRING' => :query,
9
+ 'rack.input' => :body
10
+ }
11
+
12
+ def params_hash env
13
+ env["QUERY_STRING"].split("&").collect{| param| param.split("=")}.inject({}){|params, param| params[param.first] = URI.decode(param.last); params }
14
+ end
15
+
16
+ def request_as_hash_from env
17
+ request = env.inject({}) do |memo, (k, v)|
18
+ request_key = REQUEST_KEYS[k]
19
+ memo[request_key] = v if request_key
20
+ memo
21
+ end
22
+
23
+ request[:headers] = headers_from env
24
+ body_string = request[:body].read
25
+
26
+ if body_string.empty?
27
+ request.delete :body
28
+ else
29
+ body_is_json = request[:headers]['Content-Type'] =~ /json/
30
+ request[:body] = body_is_json ? JSON.parse(body_string) : body_string
31
+ end
32
+ request[:method] = request[:method].downcase
33
+ request
34
+ end
35
+
36
+ private
37
+
38
+ def headers_from env
39
+ headers = env.reject{ |key, value| !(key.start_with?("HTTP") || key == 'CONTENT_TYPE' || key == 'CONTENT_LENGTH')}
40
+ headers.inject({}) do | hash, header |
41
+ hash[standardise_header(header.first)] = header.last
42
+ hash
43
+ end
44
+ end
45
+
46
+ def standardise_header header
47
+ header.gsub(/^HTTP_/, '').split("_").collect{|word| word[0] + word[1..-1].downcase}.join("-")
48
+ end
49
+ end
50
+ end
51
+ end