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