hs-pact-support 1.17.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +620 -0
- data/LICENSE.txt +22 -0
- data/README.md +5 -0
- data/lib/pact/array_like.rb +49 -0
- data/lib/pact/configuration.rb +193 -0
- data/lib/pact/consumer/request.rb +27 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +97 -0
- data/lib/pact/consumer_contract/file_name.rb +22 -0
- data/lib/pact/consumer_contract/headers.rb +51 -0
- data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
- data/lib/pact/consumer_contract/interaction.rb +81 -0
- data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
- data/lib/pact/consumer_contract/interaction_v2_parser.rb +57 -0
- data/lib/pact/consumer_contract/interaction_v3_parser.rb +92 -0
- data/lib/pact/consumer_contract/pact_file.rb +157 -0
- data/lib/pact/consumer_contract/provider_state.rb +34 -0
- data/lib/pact/consumer_contract/query.rb +138 -0
- data/lib/pact/consumer_contract/query_hash.rb +89 -0
- data/lib/pact/consumer_contract/query_string.rb +51 -0
- data/lib/pact/consumer_contract/request.rb +83 -0
- data/lib/pact/consumer_contract/response.rb +58 -0
- data/lib/pact/consumer_contract/service_consumer.rb +28 -0
- data/lib/pact/consumer_contract/service_provider.rb +28 -0
- data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
- data/lib/pact/consumer_contract.rb +1 -0
- data/lib/pact/errors.rb +21 -0
- data/lib/pact/helpers.rb +60 -0
- data/lib/pact/http/authorization_header_redactor.rb +32 -0
- data/lib/pact/logging.rb +14 -0
- data/lib/pact/matchers/actual_type.rb +16 -0
- data/lib/pact/matchers/base_difference.rb +39 -0
- data/lib/pact/matchers/differ.rb +153 -0
- data/lib/pact/matchers/difference.rb +13 -0
- data/lib/pact/matchers/difference_indicator.rb +26 -0
- data/lib/pact/matchers/embedded_diff_formatter.rb +60 -0
- data/lib/pact/matchers/expected_type.rb +35 -0
- data/lib/pact/matchers/extract_diff_messages.rb +76 -0
- data/lib/pact/matchers/index_not_found.rb +15 -0
- data/lib/pact/matchers/list_diff_formatter.rb +103 -0
- data/lib/pact/matchers/matchers.rb +285 -0
- data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
- data/lib/pact/matchers/no_diff_at_index.rb +18 -0
- data/lib/pact/matchers/regexp_difference.rb +13 -0
- data/lib/pact/matchers/type_difference.rb +16 -0
- data/lib/pact/matchers/unexpected_index.rb +11 -0
- data/lib/pact/matchers/unexpected_key.rb +11 -0
- data/lib/pact/matchers/unix_diff_formatter.rb +157 -0
- data/lib/pact/matchers.rb +1 -0
- data/lib/pact/matching_rules/extract.rb +91 -0
- data/lib/pact/matching_rules/jsonpath.rb +58 -0
- data/lib/pact/matching_rules/merge.rb +125 -0
- data/lib/pact/matching_rules/v3/extract.rb +94 -0
- data/lib/pact/matching_rules/v3/merge.rb +141 -0
- data/lib/pact/matching_rules.rb +30 -0
- data/lib/pact/reification.rb +56 -0
- data/lib/pact/rspec.rb +51 -0
- data/lib/pact/shared/active_support_support.rb +65 -0
- data/lib/pact/shared/dsl.rb +76 -0
- data/lib/pact/shared/form_differ.rb +32 -0
- data/lib/pact/shared/jruby_support.rb +18 -0
- data/lib/pact/shared/json_differ.rb +10 -0
- data/lib/pact/shared/key_not_found.rb +15 -0
- data/lib/pact/shared/multipart_form_differ.rb +16 -0
- data/lib/pact/shared/null_expectation.rb +31 -0
- data/lib/pact/shared/request.rb +106 -0
- data/lib/pact/shared/text_differ.rb +11 -0
- data/lib/pact/something_like.rb +49 -0
- data/lib/pact/specification_version.rb +18 -0
- data/lib/pact/support/version.rb +5 -0
- data/lib/pact/support.rb +12 -0
- data/lib/pact/symbolize_keys.rb +13 -0
- data/lib/pact/term.rb +85 -0
- data/lib/tasks/pact.rake +29 -0
- metadata +327 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pact/symbolize_keys'
|
2
|
+
module Pact
|
3
|
+
class ArrayLike
|
4
|
+
include SymbolizeKeys
|
5
|
+
|
6
|
+
attr_reader :contents
|
7
|
+
attr_reader :min
|
8
|
+
|
9
|
+
def initialize contents, options = {}
|
10
|
+
@contents = contents
|
11
|
+
@min = options[:min] || 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
{
|
16
|
+
:json_class => self.class.name,
|
17
|
+
:contents => contents,
|
18
|
+
:min => min
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def as_json opts = {}
|
23
|
+
to_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json opts = {}
|
27
|
+
as_json.to_json opts
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.json_create hash
|
31
|
+
symbolized_hash = symbolize_keys(hash)
|
32
|
+
new(symbolized_hash[:contents], {min: symbolized_hash[:min]})
|
33
|
+
end
|
34
|
+
|
35
|
+
def eq other
|
36
|
+
self == other
|
37
|
+
end
|
38
|
+
|
39
|
+
def == other
|
40
|
+
other.is_a?(ArrayLike) && other.contents == self.contents && other.min == self.min
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate
|
44
|
+
min.times.collect{ Pact::Reification.from_term contents }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'pact/matchers/embedded_diff_formatter'
|
2
|
+
require 'pact/matchers/unix_diff_formatter'
|
3
|
+
require 'pact/matchers/list_diff_formatter'
|
4
|
+
require 'pact/matchers/multipart_form_diff_formatter'
|
5
|
+
require 'pact/shared/json_differ'
|
6
|
+
require 'pact/shared/text_differ'
|
7
|
+
require 'pact/shared/form_differ'
|
8
|
+
require 'pact/shared/multipart_form_differ'
|
9
|
+
|
10
|
+
|
11
|
+
module Pact
|
12
|
+
|
13
|
+
class Configuration
|
14
|
+
|
15
|
+
DIFF_FORMATTERS = {
|
16
|
+
:embedded => Pact::Matchers::EmbeddedDiffFormatter,
|
17
|
+
:unix => Pact::Matchers::UnixDiffFormatter,
|
18
|
+
:list => Pact::Matchers::ListDiffFormatter
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
class NilMatcher
|
23
|
+
def self.=~ other
|
24
|
+
other == nil ? 0 : nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
DIFF_FORMATTER_REGISTRATIONS = [
|
29
|
+
[/multipart\/form-data/, Pact::Matchers::MultipartFormDiffFormatter],
|
30
|
+
[/.*/, Pact::Matchers::UnixDiffFormatter],
|
31
|
+
[NilMatcher, Pact::Matchers::UnixDiffFormatter]
|
32
|
+
]
|
33
|
+
|
34
|
+
DIFFERS = [
|
35
|
+
[/json/, Pact::JsonDiffer],
|
36
|
+
[/application\/x\-www\-form\-urlencoded/, Pact::FormDiffer],
|
37
|
+
[/multipart\/form-data/, Pact::MultipartFormDiffer],
|
38
|
+
[NilMatcher, Pact::TextDiffer],
|
39
|
+
[/.*/, Pact::TextDiffer]
|
40
|
+
]
|
41
|
+
|
42
|
+
|
43
|
+
DEFAULT_DIFFER = Pact::TextDiffer
|
44
|
+
|
45
|
+
attr_accessor :pact_dir
|
46
|
+
attr_accessor :log_dir
|
47
|
+
attr_accessor :tmp_dir
|
48
|
+
|
49
|
+
attr_writer :logger
|
50
|
+
|
51
|
+
attr_accessor :error_stream
|
52
|
+
attr_accessor :output_stream
|
53
|
+
attr_accessor :pactfile_write_order
|
54
|
+
attr_accessor :treat_all_number_classes_as_equivalent # when using type based matching
|
55
|
+
|
56
|
+
def self.default_configuration
|
57
|
+
c = Configuration.new
|
58
|
+
c.pact_dir = File.expand_path('./spec/pacts')
|
59
|
+
c.tmp_dir = File.expand_path('./tmp/pacts')
|
60
|
+
c.log_dir = default_log_dir
|
61
|
+
|
62
|
+
c.output_stream = $stdout
|
63
|
+
c.error_stream = $stderr
|
64
|
+
c.pactfile_write_order = :chronological
|
65
|
+
c.treat_all_number_classes_as_equivalent = true
|
66
|
+
|
67
|
+
c
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@differ_registrations = []
|
72
|
+
@diff_formatter_registrations = []
|
73
|
+
end
|
74
|
+
|
75
|
+
def logger
|
76
|
+
@logger ||= create_logger
|
77
|
+
end
|
78
|
+
|
79
|
+
# Should this be deprecated in favour of register_diff_formatter???
|
80
|
+
def diff_formatter= diff_formatter
|
81
|
+
register_diff_formatter(/.*/, diff_formatter)
|
82
|
+
register_diff_formatter(nil, diff_formatter)
|
83
|
+
end
|
84
|
+
|
85
|
+
def register_diff_formatter content_type, diff_formatter
|
86
|
+
key = content_type_regexp_for content_type
|
87
|
+
@diff_formatter_registrations << [key, diff_formatter_for(diff_formatter)]
|
88
|
+
end
|
89
|
+
|
90
|
+
def diff_formatter_for_content_type content_type
|
91
|
+
diff_formatter_registrations.find{ | registration | registration.first =~ content_type }.last
|
92
|
+
end
|
93
|
+
|
94
|
+
def register_body_differ content_type, differ
|
95
|
+
key = content_type_regexp_for content_type
|
96
|
+
validate_differ differ
|
97
|
+
@differ_registrations << [key, differ]
|
98
|
+
end
|
99
|
+
|
100
|
+
def body_differ_for_content_type content_type
|
101
|
+
differ_registrations
|
102
|
+
.find{ | registration | registration.first =~ content_type }
|
103
|
+
.tap do |it|
|
104
|
+
if content_type.nil? && it.last == Pact::TextDiffer
|
105
|
+
error_stream.puts "WARN: No content type found, performing text diff on body"
|
106
|
+
logger.warn "No content type found, performing text diff on body"
|
107
|
+
end
|
108
|
+
end.last
|
109
|
+
end
|
110
|
+
|
111
|
+
def log_path
|
112
|
+
log_dir + "/pact.log"
|
113
|
+
end
|
114
|
+
|
115
|
+
def color_enabled
|
116
|
+
# Can't use ||= when the variable might be false, it will execute the expression if it's false
|
117
|
+
defined?(@color_enabled) ? @color_enabled : true
|
118
|
+
end
|
119
|
+
|
120
|
+
def color_enabled= color_enabled
|
121
|
+
@color_enabled = color_enabled
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def diff_formatter_for input
|
127
|
+
if DIFF_FORMATTERS[input]
|
128
|
+
DIFF_FORMATTERS[input]
|
129
|
+
elsif input.respond_to?(:call)
|
130
|
+
input
|
131
|
+
else
|
132
|
+
raise "Pact diff_formatter needs to respond to call, or be in the preconfigured list: #{DIFF_FORMATTERS.keys}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def validate_differ differ
|
137
|
+
if !differ.respond_to?(:call)
|
138
|
+
raise "Pact.configuration.register_body_differ expects a differ that is a lamda or a class/object that responds to call."
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def content_type_regexp_for content_type
|
143
|
+
case content_type
|
144
|
+
when String then Regexp.new(/^#{Regexp.escape(content_type)}$/)
|
145
|
+
when Regexp then content_type
|
146
|
+
when nil then NilMatcher
|
147
|
+
else
|
148
|
+
raise "Invalid content type used to register a differ (#{content_type.inspect}). Please use a Regexp or a String."
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def differ_registrations
|
153
|
+
@differ_registrations + DIFFERS
|
154
|
+
end
|
155
|
+
|
156
|
+
def diff_formatter_registrations
|
157
|
+
@diff_formatter_registrations + DIFF_FORMATTER_REGISTRATIONS
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.default_log_dir
|
161
|
+
File.expand_path("./log")
|
162
|
+
end
|
163
|
+
|
164
|
+
#Would love a better way of determining this! It sure won't work on windows.
|
165
|
+
def is_rake_running?
|
166
|
+
`ps -ef | grep rake | grep #{Process.ppid} | grep -v 'grep'`.size > 0
|
167
|
+
end
|
168
|
+
|
169
|
+
def create_logger
|
170
|
+
FileUtils::mkdir_p log_dir
|
171
|
+
logger = ::Logger.new(log_path)
|
172
|
+
logger.level = ::Logger::DEBUG
|
173
|
+
logger
|
174
|
+
rescue Errno::EROFS
|
175
|
+
# So we can run on RunKit
|
176
|
+
logger = ::Logger.new($stdout)
|
177
|
+
logger.level = ::Logger::DEBUG
|
178
|
+
logger
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.configuration
|
183
|
+
@configuration ||= Configuration.default_configuration
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.configure
|
187
|
+
yield configuration
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.clear_configuration
|
191
|
+
@configuration = nil
|
192
|
+
end
|
193
|
+
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,97 @@
|
|
1
|
+
require 'pact/logging'
|
2
|
+
require 'pact/something_like'
|
3
|
+
require 'pact/symbolize_keys'
|
4
|
+
require 'pact/term'
|
5
|
+
require 'pact/shared/active_support_support'
|
6
|
+
require 'date'
|
7
|
+
require 'json/add/regexp'
|
8
|
+
require 'open-uri'
|
9
|
+
require 'pact/consumer_contract/service_consumer'
|
10
|
+
require 'pact/consumer_contract/service_provider'
|
11
|
+
require 'pact/consumer_contract/interaction'
|
12
|
+
require 'pact/consumer_contract/pact_file'
|
13
|
+
require 'pact/consumer_contract/http_consumer_contract_parser'
|
14
|
+
|
15
|
+
module Pact
|
16
|
+
|
17
|
+
class UnrecognizePactFormatError < ::Pact::Error; end
|
18
|
+
|
19
|
+
class ConsumerContract
|
20
|
+
|
21
|
+
include SymbolizeKeys
|
22
|
+
include Logging
|
23
|
+
include PactFile
|
24
|
+
|
25
|
+
attr_accessor :interactions
|
26
|
+
attr_accessor :consumer
|
27
|
+
attr_accessor :provider
|
28
|
+
|
29
|
+
def initialize(attributes = {})
|
30
|
+
@interactions = attributes[:interactions] || []
|
31
|
+
@consumer = attributes[:consumer]
|
32
|
+
@provider = attributes[:provider]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.add_parser consumer_contract_parser
|
36
|
+
parsers.unshift(consumer_contract_parser)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.parsers
|
40
|
+
@parsers ||= [Pact::HttpConsumerContractParser.new]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.from_hash(hash)
|
44
|
+
parsers.each do | parser |
|
45
|
+
return parser.call(hash) if parser.can_parse?(hash)
|
46
|
+
end
|
47
|
+
raise Pact::UnrecognizePactFormatError.new("This document does not use a recognised Pact format: #{hash}")
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.from_json string
|
51
|
+
deserialised_object = JSON.load(maintain_backwards_compatiblity_with_producer_keys(string))
|
52
|
+
from_hash(deserialised_object)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.from_uri uri, options = {}
|
56
|
+
from_json(Pact::PactFile.read(uri, options))
|
57
|
+
rescue UnrecognizePactFormatError
|
58
|
+
raise Pact::UnrecognizePactFormatError.new("This document does not use a recognised Pact format. Please check that #{uri} is a valid pact file.")
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.maintain_backwards_compatiblity_with_producer_keys string
|
62
|
+
string.gsub('"producer":', '"provider":').gsub('"producer_state":', '"provider_state":') if string
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_interaction criteria
|
66
|
+
interactions = find_interactions criteria
|
67
|
+
if interactions.size == 0
|
68
|
+
raise Pact::Error.new("Could not find interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}.")
|
69
|
+
elsif interactions.size > 1
|
70
|
+
raise Pact::Error.new("Found more than 1 interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}.")
|
71
|
+
end
|
72
|
+
interactions.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_interactions criteria
|
76
|
+
interactions.select{ | interaction| interaction.matches_criteria?(criteria)}
|
77
|
+
end
|
78
|
+
|
79
|
+
def each
|
80
|
+
interactions.each do | interaction |
|
81
|
+
yield interaction
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def writable_interactions
|
86
|
+
interactions.reject do |interaction|
|
87
|
+
# For the sake of backwards compatibility, only reject interactions where
|
88
|
+
# write_to_pact is explicitly set to false
|
89
|
+
interaction.respond_to?(:metadata) &&
|
90
|
+
!interaction.metadata.nil? &&
|
91
|
+
interaction.metadata.key?(:write_to_pact) &&
|
92
|
+
interaction.metadata[:write_to_pact] == false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pact
|
2
|
+
module FileName
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def file_name consumer_name, provider_name, options = {}
|
6
|
+
pid = options[:unique] ? "-#{Process.pid}" : ''
|
7
|
+
"#{filenamify(consumer_name)}-#{filenamify(provider_name)}#{pid}.json"
|
8
|
+
end
|
9
|
+
|
10
|
+
def file_path consumer_name, provider_name, pact_dir = Pact.configuration.pact_dir, options = {}
|
11
|
+
File.join(windows_safe(pact_dir), file_name(consumer_name, provider_name, options))
|
12
|
+
end
|
13
|
+
|
14
|
+
def filenamify name
|
15
|
+
name.downcase.gsub(/\s/, '_')
|
16
|
+
end
|
17
|
+
|
18
|
+
def windows_safe(pact_dir)
|
19
|
+
pact_dir.gsub("\\", "/")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Pact
|
2
|
+
|
3
|
+
class DuplicateHeaderError < StandardError; end
|
4
|
+
class InvalidHeaderNameTypeError < StandardError; end
|
5
|
+
|
6
|
+
class Headers < Hash
|
7
|
+
|
8
|
+
def initialize hash = {}
|
9
|
+
hash.each_pair do | key, value |
|
10
|
+
check_for_invalid key
|
11
|
+
self[find_matching_key(key)] = value
|
12
|
+
end
|
13
|
+
self.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def [] key
|
17
|
+
super(find_matching_key(key))
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch *args, &block
|
21
|
+
args[0] = find_matching_key(args[0]) if args.first
|
22
|
+
super(*args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def key? key
|
26
|
+
super(find_matching_key(key))
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :has_key?, :key?
|
30
|
+
alias_method :include?, :key?
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def find_matching_key key
|
35
|
+
key = key.to_s
|
36
|
+
match = keys.find { |k| k.downcase == key.downcase }
|
37
|
+
match.nil? ? key : match
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_for_invalid key
|
41
|
+
unless (String === key || Symbol === key)
|
42
|
+
raise InvalidHeaderNameTypeError.new "Header name (#{key}) must be a String or a Symbol."
|
43
|
+
end
|
44
|
+
if key? key
|
45
|
+
raise DuplicateHeaderError.new "Duplicate header found (#{find_matching_key(key)} and #{key}. Please use a comma separated single value when multiple headers with the same name are required."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pact/specification_version'
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
class HttpConsumerContractParser
|
5
|
+
include SymbolizeKeys
|
6
|
+
|
7
|
+
def call(hash)
|
8
|
+
hash = symbolize_keys(hash)
|
9
|
+
v = pact_specification_version(hash)
|
10
|
+
options = { pact_specification_version: v }
|
11
|
+
|
12
|
+
if v.after? 3
|
13
|
+
Pact.configuration.error_stream.puts "WARN: This code only knows how to parse v3 pacts, attempting to parse v#{options[:pact_specification_version]} pact using v3 code."
|
14
|
+
end
|
15
|
+
|
16
|
+
interactions = hash[:interactions].each_with_index.collect { |hash, index| Interaction.from_hash({ index: index }.merge(hash), options) }
|
17
|
+
ConsumerContract.new(
|
18
|
+
:consumer => ServiceConsumer.from_hash(hash[:consumer]),
|
19
|
+
:provider => ServiceProvider.from_hash(hash[:provider]),
|
20
|
+
:interactions => interactions
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pact_specification_version hash
|
25
|
+
# TODO handle all 3 ways of defining this...
|
26
|
+
# metadata.pactSpecificationVersion
|
27
|
+
maybe_pact_specification_version_1 = hash[:metadata] && hash[:metadata]['pactSpecification'] && hash[:metadata]['pactSpecification']['version']
|
28
|
+
maybe_pact_specification_version_2 = hash[:metadata] && hash[:metadata]['pactSpecificationVersion']
|
29
|
+
pact_specification_version = maybe_pact_specification_version_1 || maybe_pact_specification_version_2
|
30
|
+
pact_specification_version ? Pact::SpecificationVersion.new(pact_specification_version) : Pact::SpecificationVersion::NIL_VERSION
|
31
|
+
end
|
32
|
+
|
33
|
+
def can_parse?(hash)
|
34
|
+
hash.key?('interactions') || hash.key?(:interactions)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'pact/shared/active_support_support'
|
2
|
+
require 'pact/consumer_contract/interaction_parser'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
class Interaction
|
6
|
+
include ActiveSupportSupport
|
7
|
+
|
8
|
+
attr_accessor :description, :request, :response, :provider_state, :provider_states, :metadata, :_id, :index
|
9
|
+
|
10
|
+
def initialize attributes = {}
|
11
|
+
@description = attributes[:description]
|
12
|
+
@request = attributes[:request]
|
13
|
+
@response = attributes[:response]
|
14
|
+
@provider_state = attributes[:provider_state] || attributes[:providerState]
|
15
|
+
@provider_states = attributes[:provider_states]
|
16
|
+
@metadata = attributes[:metadata]
|
17
|
+
@_id = attributes[:_id]
|
18
|
+
@index = attributes[:index]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_hash hash, options = {}
|
22
|
+
InteractionParser.call(hash, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
h = { description: description }
|
27
|
+
|
28
|
+
if provider_states
|
29
|
+
h[:provider_states] = provider_states.collect(&:to_hash)
|
30
|
+
else
|
31
|
+
h[:provider_state] = provider_state
|
32
|
+
end
|
33
|
+
|
34
|
+
h[:request] = request.to_hash
|
35
|
+
h[:response] = response.to_hash
|
36
|
+
h[:metadata] = metadata
|
37
|
+
h
|
38
|
+
end
|
39
|
+
|
40
|
+
def http?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate!
|
45
|
+
raise Pact::InvalidInteractionError.new(self) unless description && request && response
|
46
|
+
end
|
47
|
+
|
48
|
+
def matches_criteria? criteria
|
49
|
+
criteria.each do | key, value |
|
50
|
+
unless match_criterion self.send(key.to_s), value
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def match_criterion target, criterion
|
58
|
+
target == criterion || (criterion.is_a?(Regexp) && criterion.match(target))
|
59
|
+
end
|
60
|
+
|
61
|
+
def == other
|
62
|
+
other.is_a?(Interaction) && to_hash == other.to_hash
|
63
|
+
end
|
64
|
+
|
65
|
+
def eq? other
|
66
|
+
self == other
|
67
|
+
end
|
68
|
+
|
69
|
+
def description_with_provider_state_quoted
|
70
|
+
provider_state ? "\"#{description}\" given \"#{provider_state}\"" : "\"#{description}\""
|
71
|
+
end
|
72
|
+
|
73
|
+
def request_modifies_resource_without_checking_response_body?
|
74
|
+
request.modifies_resource? && response.body_allows_any_value?
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
to_hash.to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'pact/specification_version'
|
2
|
+
require 'pact/consumer_contract/interaction_v2_parser'
|
3
|
+
require 'pact/consumer_contract/interaction_v3_parser'
|
4
|
+
|
5
|
+
module Pact
|
6
|
+
class InteractionParser
|
7
|
+
def self.call hash, options = {}
|
8
|
+
pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
|
9
|
+
case pact_specification_version.major
|
10
|
+
when nil, 0, 1, 2 then parse_v2_interaction(hash, pact_specification_version: pact_specification_version)
|
11
|
+
else parse_v3_interaction(hash, pact_specification_version: pact_specification_version)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_v2_interaction hash, options
|
16
|
+
InteractionV2Parser.call(hash, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse_v3_interaction hash, options
|
20
|
+
InteractionV3Parser.call(hash, options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pact/consumer_contract/request'
|
2
|
+
require 'pact/consumer_contract/response'
|
3
|
+
require 'pact/consumer_contract/provider_state'
|
4
|
+
require 'pact/consumer_contract/query'
|
5
|
+
require 'pact/symbolize_keys'
|
6
|
+
require 'pact/matching_rules'
|
7
|
+
require 'pact/errors'
|
8
|
+
|
9
|
+
module Pact
|
10
|
+
class InteractionV2Parser
|
11
|
+
|
12
|
+
include SymbolizeKeys
|
13
|
+
|
14
|
+
def self.call hash, options
|
15
|
+
request = parse_request(hash['request'], options)
|
16
|
+
response = parse_response(hash['response'], options)
|
17
|
+
provider_states = parse_provider_states(hash['providerState'] || hash['provider_state'])
|
18
|
+
metadata = parse_metadata(hash['metadata'])
|
19
|
+
Interaction.new(symbolize_keys(hash).merge(request: request,
|
20
|
+
response: response,
|
21
|
+
provider_states: provider_states,
|
22
|
+
metadata: metadata))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse_request request_hash, options
|
26
|
+
original_query_string = request_hash['query']
|
27
|
+
query_is_string = original_query_string.is_a?(String)
|
28
|
+
if query_is_string
|
29
|
+
request_hash = request_hash.dup
|
30
|
+
request_hash['query'] = Pact::Query.parse_string(request_hash['query'])
|
31
|
+
end
|
32
|
+
# The query has to be a hash at this stage for the matching rules to be applied
|
33
|
+
request_hash = Pact::MatchingRules.merge(request_hash, request_hash['matchingRules'], options)
|
34
|
+
# The original query string needs to be passed in to the constructor so it can be used
|
35
|
+
# when the request is replayed. Otherwise, we loose the square brackets because they get lost
|
36
|
+
# in the translation between string => structured object, as we don't know/store which
|
37
|
+
# query string convention was used.
|
38
|
+
if query_is_string
|
39
|
+
request_hash['query'] = Pact::QueryHash.new(request_hash['query'], original_query_string, Pact::Query.parsed_as_nested?(request_hash['query']))
|
40
|
+
end
|
41
|
+
request = Pact::Request::Expected.from_hash(request_hash)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.parse_response response_hash, options
|
45
|
+
response_hash = Pact::MatchingRules.merge(response_hash, response_hash['matchingRules'], options)
|
46
|
+
Pact::Response.from_hash(response_hash)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse_provider_states provider_state_name
|
50
|
+
provider_state_name ? [Pact::ProviderState.new(provider_state_name)] : []
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_metadata metadata_hash
|
54
|
+
symbolize_keys(metadata_hash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|