pact 1.3.3 → 1.4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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