pact 1.3.3 → 1.4.0.rc2

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 (110) hide show
  1. data/CHANGELOG.md +16 -1
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +34 -13
  4. data/README.md +3 -2
  5. data/example/animal-service/lib/animal_service/api.rb +1 -0
  6. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +0 -9
  7. data/example/zoo-app/lib/zoo_app/models/alligator.rb +2 -0
  8. data/lib/pact.rb +6 -5
  9. data/lib/pact/cli.rb +0 -26
  10. data/lib/pact/consumer/configuration.rb +0 -1
  11. data/lib/pact/consumer/configuration/configuration_extensions.rb +51 -0
  12. data/lib/pact/consumer/consumer_contract_builder.rb +1 -2
  13. data/lib/pact/consumer/interaction_builder.rb +2 -4
  14. data/lib/pact/doc/interaction_view_model.rb +9 -6
  15. data/lib/pact/doc/sort_interactions.rb +1 -1
  16. data/lib/pact/provider/rspec.rb +11 -9
  17. data/lib/pact/version.rb +1 -1
  18. data/pact.gemspec +5 -0
  19. data/spec/lib/pact/consumer/interaction_builder_spec.rb +4 -8
  20. data/spec/support/case-insensitive-response-header-matching.json +21 -0
  21. data/spec/support/case-insensitive-response-header-matching.rb +15 -0
  22. data/tasks/pact-test.rake +5 -0
  23. metadata +42 -122
  24. data/lib/pact/configuration.rb +0 -195
  25. data/lib/pact/consumer/app_manager.rb +0 -158
  26. data/lib/pact/consumer/interactions_filter.rb +0 -48
  27. data/lib/pact/consumer/mock_service.rb +0 -2
  28. data/lib/pact/consumer/mock_service/app.rb +0 -82
  29. data/lib/pact/consumer/mock_service/interaction_delete.rb +0 -33
  30. data/lib/pact/consumer/mock_service/interaction_list.rb +0 -76
  31. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +0 -73
  32. data/lib/pact/consumer/mock_service/interaction_post.rb +0 -31
  33. data/lib/pact/consumer/mock_service/interaction_replay.rb +0 -139
  34. data/lib/pact/consumer/mock_service/log_get.rb +0 -28
  35. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +0 -30
  36. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +0 -31
  37. data/lib/pact/consumer/mock_service/pact_post.rb +0 -33
  38. data/lib/pact/consumer/mock_service/rack_request_helper.rb +0 -51
  39. data/lib/pact/consumer/mock_service/verification_get.rb +0 -68
  40. data/lib/pact/consumer/mock_service_client.rb +0 -65
  41. data/lib/pact/consumer/mock_service_interaction_expectation.rb +0 -37
  42. data/lib/pact/consumer/request.rb +0 -27
  43. data/lib/pact/consumer/server.rb +0 -90
  44. data/lib/pact/consumer_contract.rb +0 -1
  45. data/lib/pact/consumer_contract/consumer_contract.rb +0 -115
  46. data/lib/pact/consumer_contract/consumer_contract_writer.rb +0 -84
  47. data/lib/pact/consumer_contract/file_name.rb +0 -19
  48. data/lib/pact/consumer_contract/headers.rb +0 -51
  49. data/lib/pact/consumer_contract/interaction.rb +0 -67
  50. data/lib/pact/consumer_contract/pact_file.rb +0 -24
  51. data/lib/pact/consumer_contract/request.rb +0 -73
  52. data/lib/pact/consumer_contract/service_consumer.rb +0 -28
  53. data/lib/pact/consumer_contract/service_provider.rb +0 -28
  54. data/lib/pact/logging.rb +0 -14
  55. data/lib/pact/matchers.rb +0 -1
  56. data/lib/pact/matchers/actual_type.rb +0 -16
  57. data/lib/pact/matchers/base_difference.rb +0 -37
  58. data/lib/pact/matchers/differ.rb +0 -153
  59. data/lib/pact/matchers/difference.rb +0 -13
  60. data/lib/pact/matchers/difference_indicator.rb +0 -26
  61. data/lib/pact/matchers/embedded_diff_formatter.rb +0 -62
  62. data/lib/pact/matchers/expected_type.rb +0 -35
  63. data/lib/pact/matchers/index_not_found.rb +0 -15
  64. data/lib/pact/matchers/list_diff_formatter.rb +0 -101
  65. data/lib/pact/matchers/matchers.rb +0 -139
  66. data/lib/pact/matchers/no_diff_indicator.rb +0 -18
  67. data/lib/pact/matchers/regexp_difference.rb +0 -13
  68. data/lib/pact/matchers/type_difference.rb +0 -16
  69. data/lib/pact/matchers/unexpected_index.rb +0 -11
  70. data/lib/pact/matchers/unexpected_key.rb +0 -11
  71. data/lib/pact/matchers/unix_diff_formatter.rb +0 -114
  72. data/lib/pact/reification.rb +0 -28
  73. data/lib/pact/rspec.rb +0 -53
  74. data/lib/pact/shared/active_support_support.rb +0 -51
  75. data/lib/pact/shared/dsl.rb +0 -76
  76. data/lib/pact/shared/jruby_support.rb +0 -18
  77. data/lib/pact/shared/json_differ.rb +0 -15
  78. data/lib/pact/shared/key_not_found.rb +0 -15
  79. data/lib/pact/shared/null_expectation.rb +0 -31
  80. data/lib/pact/shared/request.rb +0 -80
  81. data/lib/pact/shared/text_differ.rb +0 -14
  82. data/lib/pact/something_like.rb +0 -49
  83. data/lib/pact/symbolize_keys.rb +0 -12
  84. data/lib/pact/term.rb +0 -85
  85. data/spec/lib/pact/consumer/request_spec.rb +0 -24
  86. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +0 -58
  87. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +0 -180
  88. data/spec/lib/pact/consumer_contract/headers_spec.rb +0 -107
  89. data/spec/lib/pact/consumer_contract/interaction_spec.rb +0 -107
  90. data/spec/lib/pact/consumer_contract/request_spec.rb +0 -329
  91. data/spec/lib/pact/matchers/differ_spec.rb +0 -214
  92. data/spec/lib/pact/matchers/difference_spec.rb +0 -22
  93. data/spec/lib/pact/matchers/embedded_diff_formatter_spec.rb +0 -90
  94. data/spec/lib/pact/matchers/index_not_found_spec.rb +0 -21
  95. data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +0 -114
  96. data/spec/lib/pact/matchers/matchers_spec.rb +0 -500
  97. data/spec/lib/pact/matchers/regexp_difference_spec.rb +0 -20
  98. data/spec/lib/pact/matchers/type_difference_spec.rb +0 -34
  99. data/spec/lib/pact/matchers/unexpected_index_spec.rb +0 -20
  100. data/spec/lib/pact/matchers/unexpected_key_spec.rb +0 -20
  101. data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +0 -216
  102. data/spec/lib/pact/reification_spec.rb +0 -67
  103. data/spec/lib/pact/shared/dsl_spec.rb +0 -86
  104. data/spec/lib/pact/shared/json_differ_spec.rb +0 -36
  105. data/spec/lib/pact/shared/key_not_found_spec.rb +0 -20
  106. data/spec/lib/pact/shared/request_spec.rb +0 -111
  107. data/spec/lib/pact/shared/text_differ_spec.rb +0 -54
  108. data/spec/lib/pact/something_like_spec.rb +0 -21
  109. data/spec/lib/pact/term_spec.rb +0 -89
  110. data/spec/support/dsl_spec_support.rb +0 -7
@@ -1,31 +0,0 @@
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
@@ -1,139 +0,0 @@
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
@@ -1,28 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,31 +0,0 @@
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
@@ -1,33 +0,0 @@
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
@@ -1,51 +0,0 @@
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
@@ -1,68 +0,0 @@
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
@@ -1,65 +0,0 @@
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