right_develop 2.1.5 → 2.2.0
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.
- data/VERSION +1 -1
- data/bin/right_develop +4 -0
- data/lib/right_develop.rb +1 -0
- data/lib/right_develop/commands.rb +2 -1
- data/lib/right_develop/commands/server.rb +194 -0
- data/lib/right_develop/testing.rb +31 -0
- data/lib/right_develop/testing/clients.rb +36 -0
- data/lib/right_develop/testing/clients/rest.rb +34 -0
- data/lib/right_develop/testing/clients/rest/requests.rb +38 -0
- data/lib/right_develop/testing/clients/rest/requests/base.rb +305 -0
- data/lib/right_develop/testing/clients/rest/requests/playback.rb +293 -0
- data/lib/right_develop/testing/clients/rest/requests/record.rb +175 -0
- data/lib/right_develop/testing/recording.rb +33 -0
- data/lib/right_develop/testing/recording/config.rb +571 -0
- data/lib/right_develop/testing/recording/metadata.rb +805 -0
- data/lib/right_develop/testing/servers/might_api/.gitignore +3 -0
- data/lib/right_develop/testing/servers/might_api/Gemfile +6 -0
- data/lib/right_develop/testing/servers/might_api/Gemfile.lock +18 -0
- data/lib/right_develop/testing/servers/might_api/app/base.rb +323 -0
- data/lib/right_develop/testing/servers/might_api/app/echo.rb +73 -0
- data/lib/right_develop/testing/servers/might_api/app/playback.rb +46 -0
- data/lib/right_develop/testing/servers/might_api/app/record.rb +45 -0
- data/lib/right_develop/testing/servers/might_api/config.ru +8 -0
- data/lib/right_develop/testing/servers/might_api/config/init.rb +47 -0
- data/lib/right_develop/testing/servers/might_api/lib/config.rb +204 -0
- data/lib/right_develop/testing/servers/might_api/lib/logger.rb +68 -0
- data/right_develop.gemspec +30 -2
- metadata +84 -34
@@ -0,0 +1,293 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# ancestor
|
24
|
+
require 'right_develop/testing/clients/rest'
|
25
|
+
|
26
|
+
require 'rest_client'
|
27
|
+
|
28
|
+
module RightDevelop::Testing::Client::Rest::Request
|
29
|
+
|
30
|
+
# Provides a middle-ware layer that intercepts transmition of the request and
|
31
|
+
# escapes out of the execute call with a stubbed response using throw/catch.
|
32
|
+
class Playback < ::RightDevelop::Testing::Client::Rest::Request::Base
|
33
|
+
|
34
|
+
HALT_TRANSMIT = :halt_transmit
|
35
|
+
|
36
|
+
# exceptions
|
37
|
+
class PlaybackError < StandardError; end
|
38
|
+
|
39
|
+
# fake Net::HTTPResponse
|
40
|
+
class FakeNetHttpResponse
|
41
|
+
attr_reader :code, :body, :elapsed_seconds, :call_count
|
42
|
+
|
43
|
+
def initialize(response_hash, response_metadata)
|
44
|
+
@elapsed_seconds = Integer(response_hash[:elapsed_seconds] || 0)
|
45
|
+
@code = response_metadata.http_status.to_s
|
46
|
+
@headers = response_metadata.headers.inject({}) do |h, (k, v)|
|
47
|
+
h[k] = Array(v) # expected to be an array
|
48
|
+
h
|
49
|
+
end
|
50
|
+
@body = response_metadata.body # optional
|
51
|
+
@call_count = Integer(response_hash[:call_count]) || 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def [](key)
|
55
|
+
if header = @headers[key.downcase]
|
56
|
+
header.join(', ')
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_hash; @headers; end
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :throttle
|
66
|
+
|
67
|
+
def initialize(args)
|
68
|
+
if args[:throttle]
|
69
|
+
args = args.dup
|
70
|
+
@throttle = Integer(args.delete(:throttle))
|
71
|
+
if @throttle < 0 || @throttle > 100
|
72
|
+
raise ::ArgumentError, 'throttle must be a percentage between 0 and 100'
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@throttle = 0
|
76
|
+
end
|
77
|
+
super(args)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Overrides log_request to interrupt transmit before any connection is made.
|
81
|
+
#
|
82
|
+
# @raise [Symbol] always throws HALT_TRANSMIT
|
83
|
+
def log_request
|
84
|
+
super
|
85
|
+
throw(HALT_TRANSMIT, HALT_TRANSMIT)
|
86
|
+
end
|
87
|
+
|
88
|
+
RETRY_DELAY = 0.5
|
89
|
+
MAX_RETRIES = 100 # = 50 seconds; a socket usually times out in 60-120 seconds
|
90
|
+
|
91
|
+
# Overrides transmit to catch halt thrown by log_request.
|
92
|
+
#
|
93
|
+
# @param [URI[ uri of some kind
|
94
|
+
# @param [Net::HTTP] req of some kind
|
95
|
+
# @param [RestClient::Payload] of some kind
|
96
|
+
#
|
97
|
+
# @return
|
98
|
+
def transmit(uri, req, payload, &block)
|
99
|
+
caught = catch(HALT_TRANSMIT) { super }
|
100
|
+
if caught == HALT_TRANSMIT
|
101
|
+
response = nil
|
102
|
+
try_counter = 0
|
103
|
+
while response.nil?
|
104
|
+
with_state_lock do |state|
|
105
|
+
response = catch(METADATA_CLASS::HALT) do
|
106
|
+
fetch_response(state)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
case response
|
110
|
+
when METADATA_CLASS::RetryableFailure
|
111
|
+
try_counter += 1
|
112
|
+
if try_counter >= MAX_RETRIES
|
113
|
+
message =
|
114
|
+
"Released thread id=#{::Thread.current.object_id} after " <<
|
115
|
+
"#{try_counter} attempts to satisfy a retryable condition:\n" <<
|
116
|
+
response.message
|
117
|
+
raise PlaybackError, message
|
118
|
+
end
|
119
|
+
if 1 == try_counter
|
120
|
+
message = "Blocking thread id=#{::Thread.current.object_id} " <<
|
121
|
+
'until a retryable condition is satisfied...'
|
122
|
+
logger.debug(message)
|
123
|
+
end
|
124
|
+
response = nil
|
125
|
+
sleep RETRY_DELAY
|
126
|
+
else
|
127
|
+
if try_counter > 0
|
128
|
+
message = "Released thread id=#{::Thread.current.object_id} " <<
|
129
|
+
'after a retryable condition was satisfied.'
|
130
|
+
logger.debug(message)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# delay, if throttled, to simulate server response time.
|
136
|
+
if @throttle > 0 && response.elapsed_seconds > 0
|
137
|
+
delay = (Float(response.elapsed_seconds) * @throttle) / 100.0
|
138
|
+
logger.debug("throttle delay = #{delay}")
|
139
|
+
sleep delay
|
140
|
+
end
|
141
|
+
log_response(response)
|
142
|
+
process_result(response, &block)
|
143
|
+
else
|
144
|
+
raise PlaybackError,
|
145
|
+
'Unexpected RestClient::Request#transmit returned without calling RestClient::Request#log_request'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @see RightDevelop::Testing::Client::Rest::Request::Base.handle_timeout
|
150
|
+
def handle_timeout
|
151
|
+
raise ::NotImplementedError, 'Timeout is unexpected for stubbed API call.'
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
# @see RightDevelop::Testing::Client::Rest::Request::Base#recording_mode
|
157
|
+
def recording_mode
|
158
|
+
:playback
|
159
|
+
end
|
160
|
+
|
161
|
+
def fetch_response(state)
|
162
|
+
# response must exist in the current epoch (i.e. can only enter next epoch
|
163
|
+
# after a valid response is found) or in a past epoch. the latter was
|
164
|
+
# allowed due to multithreaded requests causing the epoch to advance
|
165
|
+
# (in a non-throttled playback) before all requests for a past epoch have
|
166
|
+
# been made. the current epoch is always preferred over past.
|
167
|
+
logger.debug("BEGIN playback state = #{state.inspect}") if logger.debug?
|
168
|
+
file_path = nil
|
169
|
+
past_epochs = state[:past_epochs] ||= []
|
170
|
+
try_epochs = [state[:epoch]] + past_epochs
|
171
|
+
tried_paths = []
|
172
|
+
try_epochs.each do |epoch|
|
173
|
+
file_path = response_file_path(epoch)
|
174
|
+
break if ::File.file?(file_path)
|
175
|
+
tried_paths << file_path
|
176
|
+
file_path = nil
|
177
|
+
end
|
178
|
+
if file_path
|
179
|
+
response_hash = ::Mash.new(::YAML.load_file(file_path))
|
180
|
+
@response_metadata = create_response_metadata(
|
181
|
+
state,
|
182
|
+
response_hash[:http_status],
|
183
|
+
response_hash[:headers],
|
184
|
+
response_hash[:body])
|
185
|
+
result = FakeNetHttpResponse.new(response_hash, response_metadata)
|
186
|
+
else
|
187
|
+
raise PlaybackError,
|
188
|
+
"Unable to locate response file(s): \"#{tried_paths.join("\", \"")}\""
|
189
|
+
end
|
190
|
+
logger.debug("Played back response from #{file_path.inspect}.")
|
191
|
+
|
192
|
+
# determine if epoch is done, which it is if every known request has been
|
193
|
+
# responded to for the current epoch. there is a steady state at the end
|
194
|
+
# of time when all responses are given but there is no next epoch.
|
195
|
+
unless state[:end_of_time]
|
196
|
+
|
197
|
+
# list epochs once.
|
198
|
+
unless epochs = state[:epochs]
|
199
|
+
epochs = []
|
200
|
+
::Dir[::File.join(fixtures_dir, '*')].each do |path|
|
201
|
+
if ::File.directory?(path)
|
202
|
+
name = ::File.basename(path)
|
203
|
+
epochs << Integer(name) if name =~ /^\d+$/
|
204
|
+
end
|
205
|
+
end
|
206
|
+
state[:epochs] = epochs.sort!
|
207
|
+
end
|
208
|
+
|
209
|
+
# current epoch must be listed.
|
210
|
+
current_epoch = state[:epoch]
|
211
|
+
unless current_epoch == epochs.first
|
212
|
+
raise PlaybackError,
|
213
|
+
"Unable to locate current epoch directory: #{::File.join(fixtures_dir, current_epoch.to_s).inspect}"
|
214
|
+
end
|
215
|
+
|
216
|
+
# sorted epochs reveal the future.
|
217
|
+
if next_epoch = epochs[1]
|
218
|
+
# list all responses in current epoch once.
|
219
|
+
unless remaining = state[:remaining_responses]
|
220
|
+
# use all configured route subdirectories when building remaining
|
221
|
+
# responses hash.
|
222
|
+
#
|
223
|
+
# note that any unknown route fixtures would cause playback to spin
|
224
|
+
# on the same epoch forever. we could specifically select known
|
225
|
+
# route directories but it is just easier to find all here.
|
226
|
+
search_path = ::File.join(
|
227
|
+
@fixtures_dir,
|
228
|
+
current_epoch.to_s,
|
229
|
+
'*/response/**/*.yml')
|
230
|
+
remaining = state[:remaining_responses] = ::Dir[search_path].inject({}) do |h, path|
|
231
|
+
h[path] = { call_count: 0 }
|
232
|
+
h
|
233
|
+
end
|
234
|
+
if remaining.empty?
|
235
|
+
raise PlaybackError,
|
236
|
+
"Unable to determine remaining responses from #{search_path.inspect}"
|
237
|
+
end
|
238
|
+
logger.debug("Pending responses for epoch = #{current_epoch}: #{remaining.inspect}")
|
239
|
+
end
|
240
|
+
|
241
|
+
# may have been reponded before in same epoch; only care if this is
|
242
|
+
# the first time response was used unless playback is throttled.
|
243
|
+
#
|
244
|
+
# when playback is not throttled, there is no time delay (beyond the
|
245
|
+
# time needed to compute response) and the minimum number of calls per
|
246
|
+
# response is one.
|
247
|
+
#
|
248
|
+
# when playback is throttled (non-zero) we must satisfy the call count
|
249
|
+
# before advancing epoch. the point of this is to force the client to
|
250
|
+
# repeat the request the recorded number of times before the state
|
251
|
+
# appears to change.
|
252
|
+
#
|
253
|
+
# note that the user can achieve minimum delay while checking call
|
254
|
+
# count by setting @throttle = 1
|
255
|
+
if response_data = remaining[file_path]
|
256
|
+
response_data[:call_count] += 1
|
257
|
+
exhausted_response =
|
258
|
+
(0 == @throttle) ||
|
259
|
+
(response_data[:call_count] >= result.call_count)
|
260
|
+
if exhausted_response
|
261
|
+
remaining.delete(file_path)
|
262
|
+
if remaining.empty?
|
263
|
+
# time marches on.
|
264
|
+
past_epochs.unshift(epochs.shift)
|
265
|
+
state[:epoch] = next_epoch
|
266
|
+
state.delete(:remaining_responses) # reset responses for next epoch
|
267
|
+
if logger.debug?
|
268
|
+
message = <<EOF
|
269
|
+
|
270
|
+
A new epoch = #{state[:epoch]} begins due to
|
271
|
+
verb = #{request_metadata.verb}
|
272
|
+
uri = \"#{request_metadata.uri}\"
|
273
|
+
throttle = #{@throttle}
|
274
|
+
call_count = #{@throttle == 0 ? '<ignored>' : "#{response_data[:call_count]} >= #{result.call_count}"}
|
275
|
+
EOF
|
276
|
+
logger.debug(message)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
# the future is now; no need to add final epoch to past epochs.
|
283
|
+
state.delete(:remaining_responses)
|
284
|
+
state.delete(:epochs)
|
285
|
+
state[:end_of_time] = true
|
286
|
+
end
|
287
|
+
end
|
288
|
+
logger.debug("END playback state = #{state.inspect}") if logger.debug?
|
289
|
+
result
|
290
|
+
end
|
291
|
+
|
292
|
+
end # Base
|
293
|
+
end # RightDevelop::Testing::Client::Rest
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# ancestor
|
24
|
+
require 'right_develop/testing/clients/rest'
|
25
|
+
|
26
|
+
require 'fileutils'
|
27
|
+
require 'rest_client'
|
28
|
+
|
29
|
+
module RightDevelop::Testing::Client::Rest::Request
|
30
|
+
|
31
|
+
# Provides a middle-ware layer that intercepts response by overriding the
|
32
|
+
# logging mechanism built into rest-client Request. Request supports 'before'
|
33
|
+
# hooks (for request) but not 'after' hooks (for response) so logging is all
|
34
|
+
# we have.
|
35
|
+
class Record < ::RightDevelop::Testing::Client::Rest::Request::Base
|
36
|
+
|
37
|
+
# simulated 504 Net::HTTPResponse
|
38
|
+
class TimeoutNetHttpResponse
|
39
|
+
attr_reader :code, :body
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
message = 'Timeout'
|
43
|
+
@code = 504
|
44
|
+
@headers = {
|
45
|
+
'content-type' => 'text/plain',
|
46
|
+
'content-length' => ::Rack::Utils.bytesize(message).to_s,
|
47
|
+
'connection' => 'close',
|
48
|
+
}.inject({}) do |h, (k, v)|
|
49
|
+
h[k] = Array(v) # expected to be an array
|
50
|
+
h
|
51
|
+
end
|
52
|
+
@body = message
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](key)
|
56
|
+
if header = @headers[key.downcase]
|
57
|
+
header.join(', ')
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_hash; @headers; end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Overrides log_request for basic logging.
|
67
|
+
#
|
68
|
+
# @param [RestClient::Response] to capture
|
69
|
+
#
|
70
|
+
# @return [Object] undefined
|
71
|
+
def log_request
|
72
|
+
logger.debug("proxied_url = #{@url.inspect}")
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# Overrides log_response to capture both request and response.
|
77
|
+
#
|
78
|
+
# @param [RestClient::Response] to capture
|
79
|
+
#
|
80
|
+
# @return [Object] undefined
|
81
|
+
def log_response(response)
|
82
|
+
result = super
|
83
|
+
with_state_lock { |state| record_response(state, response) }
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
# @see RightDevelop::Testing::Client::Rest::Request::Base.handle_timeout
|
88
|
+
def handle_timeout
|
89
|
+
super
|
90
|
+
response = TimeoutNetHttpResponse.new
|
91
|
+
with_state_lock { |state| record_response(state, response) }
|
92
|
+
response
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
# @see RightDevelop::Testing::Client::Rest::Request::Base#recording_mode
|
98
|
+
def recording_mode
|
99
|
+
:record
|
100
|
+
end
|
101
|
+
|
102
|
+
def record_response(state, response)
|
103
|
+
# never record redirects because a redirect cannot be proxied back to the
|
104
|
+
# client (i.e. the client cannot update it's request url when proxied).
|
105
|
+
code = response.code
|
106
|
+
http_status = Integer(code)
|
107
|
+
if http_status >= 300 && http_status < 400
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
# use raw headers for response instead of the usual RestClient behavior of
|
112
|
+
# converting arrays to comma-delimited strings.
|
113
|
+
@response_metadata = create_response_metadata(
|
114
|
+
state, http_status, response.to_hash, response.body)
|
115
|
+
|
116
|
+
# record elapsed time in (integral) seconds. not intended to be a precise
|
117
|
+
# measure of time but rather used to throttle server if client is time-
|
118
|
+
# sensitive for some reason.
|
119
|
+
elapsed_seconds = @response_timestamp - @request_timestamp
|
120
|
+
response_hash = {
|
121
|
+
elapsed_seconds: elapsed_seconds,
|
122
|
+
http_status: response_metadata.http_status,
|
123
|
+
headers: response_metadata.headers.to_hash,
|
124
|
+
body: response_metadata.body,
|
125
|
+
}
|
126
|
+
|
127
|
+
# detect collision, if any, to determine if we have entered a new epoch.
|
128
|
+
ork = outstanding_request_key
|
129
|
+
call_count = 0
|
130
|
+
next_checksum = response_metadata.checksum
|
131
|
+
if response_data = (state[:response_data] ||= {})[ork]
|
132
|
+
last_checksum = response_data[:checksum]
|
133
|
+
if last_checksum != next_checksum
|
134
|
+
# note that variables never reset due to epoch change but they can be
|
135
|
+
# updated by a subsequent client request.
|
136
|
+
state[:epoch] += 100 # leave room to insert custom epochs
|
137
|
+
state[:response_data] = {} # reset checksums for next epoch
|
138
|
+
logger.debug("A new epoch = #{state[:epoch]} begins due to #{request_metadata.verb} \"#{request_metadata.uri}\"")
|
139
|
+
else
|
140
|
+
call_count = response_data[:call_count]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
call_count += 1
|
144
|
+
state[:response_data][ork] = {
|
145
|
+
checksum: next_checksum,
|
146
|
+
call_count: call_count,
|
147
|
+
}
|
148
|
+
response_hash[:call_count] = call_count
|
149
|
+
|
150
|
+
# write request unless already written.
|
151
|
+
file_path = request_file_path(state[:epoch])
|
152
|
+
unless ::File.file?(file_path)
|
153
|
+
# note that variables are not recorded because they must always be
|
154
|
+
# supplied by the client's request.
|
155
|
+
request_hash = {
|
156
|
+
verb: request_metadata.verb,
|
157
|
+
query: request_metadata.query,
|
158
|
+
headers: request_metadata.headers.to_hash,
|
159
|
+
body: request_metadata.body
|
160
|
+
}
|
161
|
+
::FileUtils.mkdir_p(::File.dirname(file_path))
|
162
|
+
::File.open(file_path, 'w') { |f| f.puts(::YAML.dump(request_hash)) }
|
163
|
+
end
|
164
|
+
logger.debug("Recorded request at #{file_path.inspect}.")
|
165
|
+
|
166
|
+
# response always written for incremented call count.
|
167
|
+
file_path = response_file_path(state[:epoch])
|
168
|
+
::FileUtils.mkdir_p(::File.dirname(file_path))
|
169
|
+
::File.open(file_path, 'w') { |f| f.puts(::YAML.dump(response_hash)) }
|
170
|
+
logger.debug("Recorded response at #{file_path.inspect}.")
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
end # Base
|
175
|
+
end # RightDevelop::Testing::Client::Rest
|