-
Notifications
You must be signed in to change notification settings - Fork 68
/
consumer_contract_writer.rb
182 lines (151 loc) · 6.18 KB
/
consumer_contract_writer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
require 'pact/consumer_contract'
require 'pact/mock_service/interactions/interactions_filter'
require 'pact/consumer_contract/file_name'
require 'pact/consumer_contract/pact_file'
require 'pact/consumer_contract/consumer_contract_decorator'
require 'pact/shared/active_support_support'
require 'fileutils'
module Pact
class ConsumerContractWriterError < StandardError; end
class ConsumerContractWriter
DEFAULT_PACT_SPECIFICATION_VERSION = '2.0.0'
include Pact::FileName
include Pact::PactFile
include ActiveSupportSupport
def initialize consumer_contract_details, logger
@logger = logger
@consumer_contract_details = consumer_contract_details
@pactfile_write_mode = (consumer_contract_details[:pactfile_write_mode] || :overwrite).to_sym
@interactions = consumer_contract_details.fetch(:interactions)
@pact_specification_version = (consumer_contract_details[:pact_specification_version] || DEFAULT_PACT_SPECIFICATION_VERSION).to_s
@consumer_contract_decorator_class = consumer_contract_details[:consumer_contract_decorator_class] || Pact::ConsumerContractDecorator
@error_stream = consumer_contract_details[:error_stream] || Pact.configuration.error_stream
@output_stream = consumer_contract_details[:output_stream] || Pact.configuration.output_stream
end
def consumer_contract
@consumer_contract ||= Pact::ConsumerContract.new(
consumer: ServiceConsumer.new(name: consumer_contract_details[:consumer][:name]),
provider: ServiceProvider.new(name: consumer_contract_details[:provider][:name]),
interactions: interactions_for_new_consumer_contract)
end
def write
update_pactfile_if_needed
pact_json
end
def can_write?
consumer_name && provider_name && consumer_contract_details[:pact_dir]
end
private
attr_reader :consumer_contract_details, :pactfile_write_mode, :interactions, :logger, :pact_specification_version, :consumer_contract_decorator_class
attr_reader :error_stream, :output_stream
def update_pactfile_if_needed
return if pactfile_write_mode == :none
return if interactions.count == 0
update_pactfile
end
def update_pactfile
logger.info log_message
FileUtils.mkdir_p File.dirname(pactfile_path)
# must be read after obtaining the lock, and must be read from the yielded file object, otherwise windows freaks out
# https://apidock.com/ruby/File/flock
File.open(pactfile_path, File::RDWR|File::CREAT, 0644) {|pact_file|
pact_file.flock(File::LOCK_EX)
@existing_contents = pact_file.read
new_contents = pact_json
pact_file.rewind
pact_file.truncate 0
pact_file.write new_contents
pact_file.flush
pact_file.truncate(pact_file.pos)
}
end
def pact_json
@pact_json ||= fix_json_formatting(JSON.pretty_generate(decorated_pact))
end
def decorated_pact
@decorated_pact ||= consumer_contract_decorator_class.new(consumer_contract, pact_specification_version: pact_specification_version)
end
def interactions_for_new_consumer_contract
if pactfile_exists? && (updating? || merging?)
merged_interactions = existing_interactions.dup
filter = Pact::MockService::Interactions.filter(merged_interactions, pactfile_write_mode)
interactions.each {|i| filter << i }
merged_interactions
else
interactions
end
end
def existing_interactions
interactions = []
if pactfile_exists? && pactfile_has_contents?
begin
interactions = existing_consumer_contract.interactions
print_updating_warning if updating?
rescue StandardError => e
warn_and_stderr "Could not load existing consumer contract from #{pactfile_path} due to #{e}. Creating a new file."
logger.error e
logger.error e.backtrace
end
end
interactions
end
def pactfile_exists?
File.exist?(pactfile_path)
end
def pactfile_has_contents?
File.size(pactfile_path) != 0
end
def existing_contents
@existing_contents
end
def existing_consumer_contract
@existing_consumer_contract ||= Pact::ConsumerContract.from_json(existing_contents)
end
def warn_and_stderr msg
error_stream.puts msg
logger.warn msg
end
def info_and_puts msg
output_stream.puts msg
logger.info msg
end
def consumer_name
consumer_contract_details[:consumer][:name]
end
def provider_name
consumer_contract_details[:provider][:name]
end
def pactfile_path
raise 'You must specify a consumer and provider name' unless (consumer_name && provider_name)
file_path consumer_name, provider_name, pact_dir, unique: consumer_contract_details[:unique_pact_file_names]
end
def pact_dir
unless consumer_contract_details[:pact_dir]
raise ConsumerContractWriterError.new("Please indicate the directory to write the pact to by specifying the pact_dir field")
end
consumer_contract_details[:pact_dir]
end
def updating?
pactfile_write_mode == :update
end
def merging?
pactfile_write_mode == :merge
end
def log_message
if updating?
"Updating pact for #{provider_name} at #{pactfile_path}"
elsif merging?
"Merging interactions into pact for #{provider_name} at #{pactfile_path}"
else
"Writing pact for #{provider_name} to #{pactfile_path}"
end
end
def print_updating_warning
info_and_puts "*****************************************************************************"
info_and_puts "Updating existing file .#{pactfile_path.gsub(Dir.pwd, '')} as pactfile_write_mode is :update"
info_and_puts "Only interactions defined in this test run will be updated."
info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
info_and_puts "*****************************************************************************"
end
end
end